#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
#include "mlir/Dialect/Vector/IR/VectorOps.h"
#include "mlir/Dialect/Affine/IR/AffineOps.h"
#include "mlir/Dialect/X86Vector/X86VectorDialect.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Pass/PassRegistry.h"
#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
#include "mlir/Transforms/DialectConversion.h"

#include "crypto/Crypto.h"

namespace mlir {
#define GEN_PASS_DEF_CONVERTCRYPTOTOARM
#include "mlir/Conversion/Passes.h.inc"
} // namespace mlir


using namespace mlir;
using namespace vector;
using namespace mlir::arith;

//===----------------------------------------------------------------------===//
// Operation conversion
//===----------------------------------------------------------------------===//

#define CHECK_ATTR(op) \
  do{ \
    auto device_attr = op->getAttrOfType<StringAttr>("device"); \
    if (!device_attr) { \
      return failure(); \
    }  \
    auto device_str = device_attr.getValue(); \
    if (!device_str.contains("cpu")) { \
      return failure(); \
    } \
  } while(0);



namespace {

Value getCustomSHA1msg1Op(mlir::Location loc, PatternRewriter &rewriter, VectorType vectype,  Value c0, Value c1, Value c2, Value c3, Value data1, Value data2) {
  Value tmp_v = rewriter.create<vector::ShuffleOp>(loc, data1, data2, SmallVector<int64_t>{1, 0, 7, 6});
  tmp_v = rewriter.create<vector::ShuffleOp>(loc, tmp_v, tmp_v, SmallVector<int64_t>{3, 2, 1, 0});
  return rewriter.create<arith::XOrIOp>(loc, data1, tmp_v);
}

Value getCustomSHA1msg2Op(mlir::Location loc, PatternRewriter &rewriter, VectorType vectype, Value c0, Value c1, Value c2, Value c3, Value c31_i32, Value c1_i32, Value data1, Value data2) {
    Value a3 = rewriter.create<vector::ExtractElementOp>(loc, data1, c3);
    Value b2 = rewriter.create<vector::ExtractElementOp>(loc, data2, c2);
    Value xor_result = rewriter.create<arith::XOrIOp>(loc, a3, b2);

    Value xor_result_rol = rewriter.create<arith::ShLIOp>(loc, xor_result, c1_i32);
    Value xor_result_ror = rewriter.create<arith::ShRUIOp>(loc, xor_result, c31_i32);
    Value xor_result_final = rewriter.create<arith::OrIOp>(loc, xor_result_rol, xor_result_ror);
    
    Value w_13_16 = rewriter.create<vector::InsertElementOp>(loc, vectype, xor_result_final, data2, c3);
    w_13_16 = rewriter.create<vector::ShuffleOp>(loc, w_13_16, w_13_16, SmallVector<int64_t>{3, 0, 1, 2});

    Value xor_vec = rewriter.create<arith::XOrIOp>(loc, w_13_16, data1);

    SmallVector<Value, 4> shiftedValues;

    Value value_0 = rewriter.create<vector::ExtractElementOp>(loc, xor_vec, c0);
    Value value_rol_0 = rewriter.create<arith::ShLIOp>(loc, value_0, c1_i32);
    Value value_ror_0 = rewriter.create<arith::ShRUIOp>(loc, value_0, c31_i32);
    shiftedValues.push_back(rewriter.create<arith::OrIOp>(loc, value_rol_0, value_ror_0));

    Value value_1 = rewriter.create<vector::ExtractElementOp>(loc, xor_vec, c1);
    Value value_rol_1 = rewriter.create<arith::ShLIOp>(loc, value_1, c1_i32);
    Value value_ror_1 = rewriter.create<arith::ShRUIOp>(loc, value_1, c31_i32);
    shiftedValues.push_back(rewriter.create<arith::OrIOp>(loc, value_rol_1, value_ror_1));

    Value value_2 = rewriter.create<vector::ExtractElementOp>(loc, xor_vec, c2);
    Value value_rol_2 = rewriter.create<arith::ShLIOp>(loc, value_2, c1_i32);
    Value value_ror_2 = rewriter.create<arith::ShRUIOp>(loc, value_2, c31_i32);
    shiftedValues.push_back(rewriter.create<arith::OrIOp>(loc, value_rol_2, value_ror_2));

    Value value_3 = rewriter.create<vector::ExtractElementOp>(loc, xor_vec, c3);
    Value value_rol_3 = rewriter.create<arith::ShLIOp>(loc, value_3, c1_i32);
    Value value_ror_3 = rewriter.create<arith::ShRUIOp>(loc, value_3, c31_i32);
    shiftedValues.push_back(rewriter.create<arith::OrIOp>(loc, value_rol_3, value_ror_3));

    Value result = rewriter.create<vector::BroadcastOp>(loc, vectype, shiftedValues[2]);
    result = rewriter.create<vector::InsertElementOp>(loc, vectype, shiftedValues[3], result, c3);
    result = rewriter.create<vector::InsertElementOp>(loc, vectype, shiftedValues[1], result, c1);
    result = rewriter.create<vector::InsertElementOp>(loc, vectype, shiftedValues[0], result, c0);

    return result;
}

Value getCustomSHA1nexteOp(mlir::Location loc, PatternRewriter &rewriter, VectorType vectype,  Value c0, Value c1, Value c2, Value c3, Value c0_I32, Value c2_I32, Value c30_I32, Value data1, Value data2) {
  Value a3 = rewriter.create<vector::ExtractElementOp>(loc, data1, c3);
  Value a3_shifted_left = rewriter.create<arith::ShLIOp>(loc, a3, c30_I32);
  Value a3_shifted_right = rewriter.create<arith::ShRUIOp>(loc, a3, c2_I32);
  Value a3_shifted = rewriter.create<arith::OrIOp>(loc, a3_shifted_left, a3_shifted_right);

  Value tmp = rewriter.create<vector::InsertElementOp>(loc, vectype, a3_shifted, data1, c3);
  tmp = rewriter.create<vector::InsertElementOp>(loc, vectype, c0_I32, tmp, c2);
  tmp = rewriter.create<vector::InsertElementOp>(loc, vectype, c0_I32, tmp, c1);
  tmp = rewriter.create<vector::InsertElementOp>(loc, vectype, c0_I32, tmp, c0);

  return rewriter.create<arith::AddIOp>(loc, tmp, data2);
}

Value getCustomSHA1rnds4Op(mlir::Location loc, PatternRewriter &rewriter, VectorType vectype,  Value c0, Value c1, Value c2, Value c3, Value c2_I32, Value c5_I32, Value c27_I32, Value c30_I32, Value data1, Value data2, Value func){

  Value _a = rewriter.create<vector::ExtractElementOp>(loc, data1, c3);
  Value _b = rewriter.create<vector::ExtractElementOp>(loc, data1, c2);
  Value _c = rewriter.create<vector::ExtractElementOp>(loc, data1, c1);
  Value _d = rewriter.create<vector::ExtractElementOp>(loc, data1, c0);
  
  Value W_0 = rewriter.create<vector::ExtractElementOp>(loc, data2, c3);
  Value W_1 = rewriter.create<vector::ExtractElementOp>(loc, data2, c2);
  Value W_2 = rewriter.create<vector::ExtractElementOp>(loc, data2, c1);
  Value W_3 = rewriter.create<vector::ExtractElementOp>(loc, data2, c0);

  uint32_t K = func == c0 ? 0x5a827999 : 
               func == c1 ? 0x6ed9eba1 :
               func == c2 ? 0x8f1bbcdc : 0xca62c1d6;
  Value KValue = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(K));
  
  auto f = [&func, &c0, &c1, &c2, &loc, &rewriter](Value x, Value y, Value z) {
    Value and_op1 = rewriter.create<arith::AndIOp>(loc, x, y);
    Value and_op2 = rewriter.create<arith::AndIOp>(loc, x, z);
    Value and_op3 = rewriter.create<arith::AndIOp>(loc, y, z);
    Value or_op = rewriter.create<arith::OrIOp>(loc, and_op1, and_op2);
    Value x_not = rewriter.create<arith::XOrIOp>(loc, x, rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(-1)));
    Value and_op4 = rewriter.create<arith::AndIOp>(loc, x_not, z);
    Value xor_op1 = rewriter.create<arith::XOrIOp>(loc, x, y);

    Value func0 = rewriter.create<arith::OrIOp>(loc, and_op1, and_op4);
    Value func1 = rewriter.create<arith::XOrIOp>(loc, xor_op1, z);
    Value func2 = rewriter.create<arith::OrIOp>(loc, or_op, and_op3);

    if (func == c0) {
      return func0;
    } else if (func == c1) {
      return func1;
    } else if (func == c2) {
      return func2;
    } else {
      return func1;
    }
  };

  Value fi = f(_b, _c, _d);
  Value shl_op = rewriter.create<arith::ShLIOp>(loc, _a, c5_I32);
  Value shr_op = rewriter.create<arith::ShRUIOp>(loc, _a, c27_I32);
  Value rol_op = rewriter.create<arith::OrIOp>(loc, shl_op, shr_op);
  Value add_op1 = rewriter.create<arith::AddIOp>(loc, fi, rol_op);
  Value add_op2 = rewriter.create<arith::AddIOp>(loc, add_op1, W_0);
  Value add_op3 = rewriter.create<arith::AddIOp>(loc, add_op2, KValue);

  Value A_0 = add_op3;
  Value B_0 = _a;
  Value C_0 = rewriter.create<arith::OrIOp>(loc, rewriter.create<arith::ShLIOp>(loc, _b, c30_I32), rewriter.create<arith::ShRUIOp>(loc, _b, c2_I32));
  Value D_0 = _c;
  Value E_0 = _d;

  fi = f(B_0, C_0, D_0);
  shl_op = rewriter.create<arith::ShLIOp>(loc, A_0, c5_I32);
  shr_op = rewriter.create<arith::ShRUIOp>(loc, A_0, c27_I32);
  rol_op = rewriter.create<arith::OrIOp>(loc, shl_op, shr_op);
  add_op1 = rewriter.create<arith::AddIOp>(loc, fi, rol_op);
  add_op2 = rewriter.create<arith::AddIOp>(loc, add_op1, W_1);
  add_op3 = rewriter.create<arith::AddIOp>(loc, add_op2, KValue);
  Value A_1 = rewriter.create<arith::AddIOp>(loc, add_op3, E_0);
  Value B_1 = A_0;
  Value C_1 = rewriter.create<arith::OrIOp>(loc, rewriter.create<arith::ShLIOp>(loc, B_0, c30_I32), rewriter.create<arith::ShRUIOp>(loc, B_0, c2_I32));
  Value D_1 = C_0;
  Value E_1 = D_0;

  fi = f(B_1, C_1, D_1);
  shl_op = rewriter.create<arith::ShLIOp>(loc, A_1, c5_I32);
  shr_op = rewriter.create<arith::ShRUIOp>(loc, A_1, c27_I32);
  rol_op = rewriter.create<arith::OrIOp>(loc, shl_op, shr_op);
  add_op1 = rewriter.create<arith::AddIOp>(loc, fi, rol_op);
  add_op2 = rewriter.create<arith::AddIOp>(loc, add_op1, W_2);
  add_op3 = rewriter.create<arith::AddIOp>(loc, add_op2, KValue);
  Value A_2 = rewriter.create<arith::AddIOp>(loc, add_op3, E_1);
  Value B_2 = A_1;
  Value C_2 = rewriter.create<arith::OrIOp>(loc, rewriter.create<arith::ShLIOp>(loc, B_1, c30_I32), rewriter.create<arith::ShRUIOp>(loc, B_1, c2_I32));
  Value D_2 = C_1;
  Value E_2 = D_1;

  fi = f(B_2, C_2, D_2);
  shl_op = rewriter.create<arith::ShLIOp>(loc, A_2, c5_I32);
  shr_op = rewriter.create<arith::ShRUIOp>(loc, A_2, c27_I32);
  rol_op = rewriter.create<arith::OrIOp>(loc, shl_op, shr_op);
  add_op1 = rewriter.create<arith::AddIOp>(loc, fi, rol_op);
  add_op2 = rewriter.create<arith::AddIOp>(loc, add_op1, W_3);
  add_op3 = rewriter.create<arith::AddIOp>(loc, add_op2, KValue);
  Value A_3 = rewriter.create<arith::AddIOp>(loc, add_op3, E_2);
  Value B_3 = A_2;
  Value C_3 = rewriter.create<arith::OrIOp>(loc, rewriter.create<arith::ShLIOp>(loc, B_2, c30_I32), rewriter.create<arith::ShRUIOp>(loc, B_2, c2_I32));
  Value D_3 = C_2;
  // Value E_3 = D_2;

  Value result = rewriter.create<vector::InsertElementOp>(loc, vectype, A_3, data1, c3);
  result = rewriter.create<vector::InsertElementOp>(loc, vectype, B_3, result, c2);
  result = rewriter.create<vector::InsertElementOp>(loc, vectype, C_3, result, c1);
  result = rewriter.create<vector::InsertElementOp>(loc, vectype, D_3, result, c0);

  return result;
}

class Sha1coreLoweringWithoutSHA1ISAPattern : public OpRewritePattern<crypto::Sha1core_OP> {
public:
  using OpRewritePattern<crypto::Sha1core_OP>::OpRewritePattern;

  LogicalResult matchAndRewrite(crypto::Sha1core_OP op,
                                PatternRewriter &rewriter) const override {
    CHECK_ATTR(op)
    auto loc = op->getLoc();
    Value plaintext = op->getOperand(0);      // padded already
    Value ciphertext = op->getOperand(1);

    Value c0 = rewriter.create<ConstantIndexOp>(loc, 0);
    Value c1 = rewriter.create<ConstantIndexOp>(loc, 1);
    Value c2 = rewriter.create<ConstantIndexOp>(loc, 2);
    Value c3 = rewriter.create<ConstantIndexOp>(loc, 3);
    Value c16 = rewriter.create<ConstantIndexOp>(loc, 16);
    Value c64 = rewriter.create<ConstantIndexOp>(loc, 64);
    Value cN = rewriter.create<memref::DimOp>(loc, /*source=*/plaintext, /*index=*/c0);

    // Initialization
    SmallVector<int> abcd_inverse = {
                                271733878/*0x10325476*/,
                                -1732584194/*0x98BADCFE*/,
                                -271733879/*0xEFCDAB89*/,
                                1732584193 /*0x67452301*/};
    SmallVector<int> e_inverse = {0, 0, 0, -1009589776 /*0xC3D2E1F0*/};
    Value abcd_input = rewriter.create<ConstantOp>(loc, rewriter.getI32VectorAttr(abcd_inverse));
    Value e_input = rewriter.create<ConstantOp>(loc, rewriter.getI32VectorAttr(e_inverse));

    Value c31_I32 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(31));
    Value c30_I32 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(30));
    Value c27_I32 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(27));
    Value c5_I32 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(5));
    Value c2_I32 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(2));
    Value c1_I32 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(1));
    Value c0_I32 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0));

    SmallVector<int64_t> inv16{15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0};
    VectorType vectorI8x16 = VectorType::get({16}, rewriter.getI8Type());
    VectorType vectorI32x4 = VectorType::get({4}, rewriter.getI32Type());

    scf::ForOp forOp = rewriter.create<scf::ForOp>(
      loc, c0, cN, c64, ValueRange{abcd_input, e_input},
      [&](OpBuilder &builder, Location loc, Value ivs, ValueRange iargs){
        Value abcd = iargs[0], e = iargs[1];
        Value idx = ivs;

        // Save the original hash value
        Value abcd_0 = abcd;
        Value e_0 = e;

        // Rounds 0-3
        Value input0 = rewriter.create<vector::LoadOp>(loc, vectorI8x16, plaintext, idx);
        input0 = rewriter.create<vector::ShuffleOp>(loc, input0, input0, inv16);
        Value data_0 = rewriter.create<vector::BitCastOp>(loc, vectorI32x4, input0);
        e = rewriter.create<arith::AddIOp>(loc, e, data_0);
        Value e_1 = abcd;
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e, c0);

        // Rounds 4-7
        idx = rewriter.create<arith::AddIOp>(loc, idx, c16);
        Value input1 = rewriter.create<vector::LoadOp>(loc, vectorI8x16, plaintext, idx);
        input1 = rewriter.create<vector::ShuffleOp>(loc, input1, input1, inv16);
        Value data_1 = rewriter.create<vector::BitCastOp>(loc, vectorI32x4, input1);
        e_1 = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e_1, data_1);
        e = abcd;
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e_1, c0);
        data_0 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_0, data_1);

        // Rounds 8-11
        idx = rewriter.create<arith::AddIOp>(loc, idx, c16);
        Value input2 = rewriter.create<vector::LoadOp>(loc, vectorI8x16, plaintext, idx);
        input2 = rewriter.create<vector::ShuffleOp>(loc, input2, input2, inv16);
        Value data_2 = rewriter.create<vector::BitCastOp>(loc, vectorI32x4, input2);
        e = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e, data_2);
        e_1 = abcd;
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e, c0);
        data_1 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_1, data_2);
        data_0 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_0, data_2);

        // Rounds 12-15
        idx = rewriter.create<arith::AddIOp>(loc, idx, c16);
        Value input3 = rewriter.create<vector::LoadOp>(loc, vectorI8x16, plaintext, idx);
        input3 = rewriter.create<vector::ShuffleOp>(loc, input3, input3, inv16);
        Value data_3 = rewriter.create<vector::BitCastOp>(loc, vectorI32x4, input3);
        e_1 = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e_1, data_3);
        e = abcd;
        data_0 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, c1_I32, data_0, data_3);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e_1, c0);
        data_2 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_2, data_3);
        data_1 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_1, data_3);

        // Rounds 16-19
        e = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e, data_0);
        e_1 = abcd;
        data_1 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, c1_I32, data_1, data_0);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e, c0);
        data_3 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_3, data_0);
        data_2 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_2, data_0);

        // Rounds 20-23
        e_1 = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e_1, data_1);
        e = abcd;
        data_2 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, c1_I32, data_2, data_1);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e_1, c1);
        data_0 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_0, data_1);
        data_3 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_3, data_1);

        // Rounds 24-27
        e = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e, data_2);
        e_1 = abcd;
        data_3 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, c1_I32, data_3, data_2);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e, c1);
        data_1 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_1, data_2);
        data_0 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_0, data_2);

        // Rounds 28-31
        e_1 = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e_1, data_3);
        e = abcd;
        data_0 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, c1_I32, data_0, data_3);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e_1, c1);
        data_2 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_2, data_3);
        data_1 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_1, data_3);

        // Rounds 32-35
        e = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e, data_0);
        e_1 = abcd;
        data_1 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, c1_I32, data_1, data_0);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e, c1);
        data_3 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_3, data_0);
        data_2 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_2, data_0);

        // Rounds 36-39
        e_1 = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e_1, data_1);
        e = abcd;
        data_2 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, c1_I32, data_2, data_1);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e_1, c1);
        data_0 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_0, data_1);
        data_3 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_3, data_1);

        // Rounds 40-43
        e = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e, data_2);
        e_1 = abcd;
        data_3 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, c1_I32, data_3, data_2);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e, c2);
        data_1 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_1, data_2);
        data_0 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_0, data_2);

        // Rounds 44-47
        e_1 = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e_1, data_3);
        e = abcd;
        data_0 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, c1_I32, data_0, data_3);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e_1, c2);
        data_2 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_2, data_3);
        data_1 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_1, data_3);

        // Rounds 48-51
        e = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e, data_0);
        e_1 = abcd;
        data_1 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, c1_I32, data_1, data_0);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e, c2);
        data_3 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_3, data_0);
        data_2 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_2, data_0);

        // Rounds 52-55
        e_1 = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e_1, data_1);
        e = abcd;
        data_2 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, c1_I32, data_2, data_1);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e_1, c2);
        data_0 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_0, data_1);
        data_3 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_3, data_1);

        // Rounds 56-59
        e = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e, data_2);
        e_1 = abcd;
        data_3 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, c1_I32, data_3, data_2);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e, c2);
        data_1 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_1, data_2);
        data_0 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_0, data_2);

        // Rounds 60-63
        e_1 = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e_1, data_3);
        e = abcd;
        data_0 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, c1_I32, data_0, data_3);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e_1, c3);
        data_2 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_2, data_3);
        data_1 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_1, data_3);

        // Rounds 64-67
        e = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e, data_0);
        e_1 = abcd;
        data_1 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, c1_I32, data_1, data_0);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e, c3);
        data_3 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_3, data_0);
        data_2 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_2, data_0);

        // Rounds 68-71
        e_1 = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e_1, data_1);
        e = abcd;
        data_2 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, c1_I32, data_2, data_1);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e_1, c3);
        data_3 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_3, data_1);

        // Rounds 72-75
        e = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e, data_2);
        e_1 = abcd;
        data_3 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, c1_I32, data_3, data_2);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e, c3);

        // Rounds 76-79
        e_1 = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e_1, data_3);
        e = abcd;
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e_1, c3);

        // Final
        e = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e, e_0);
        abcd = builder.create<arith::AddIOp>(loc, abcd, abcd_0);

        builder.create<scf::YieldOp>(loc, std::vector<Value>{abcd, e});
      }
    );

    // Save the final hash value

    VectorType vectorI8x20 = VectorType::get({20}, rewriter.getI8Type());
    VectorType vectorI32x5 = VectorType::get({5}, rewriter.getI32Type());

    Value abcd = forOp.getResults()[0];
    Value e = forOp.getResults()[1];
    SmallVector<int64_t> inv4{3,2,1,0};
    abcd = rewriter.create<vector::ShuffleOp>(loc, abcd, abcd, inv4);

    Value e_3 = rewriter.create<vector::ExtractElementOp>(loc, e, c3);
    Value abcde = rewriter.create<vector::SplatOp>(loc, e_3, vectorI32x5);
    abcde = rewriter.create<vector::InsertOp>(loc, rewriter.create<vector::ExtractElementOp>(loc, abcd, c0), abcde, c0);
    abcde = rewriter.create<vector::InsertOp>(loc, rewriter.create<vector::ExtractElementOp>(loc, abcd, c1), abcde, c1);
    abcde = rewriter.create<vector::InsertOp>(loc, rewriter.create<vector::ExtractElementOp>(loc, abcd, c2), abcde, c2);
    abcde = rewriter.create<vector::InsertOp>(loc, rewriter.create<vector::ExtractElementOp>(loc, abcd, c3), abcde, c3);

    SmallVector<int64_t> inv20{3,2,1,0,7,6,5,4,11,10,9,8,15,14,13,12,19,18,17,16};
    abcde = rewriter.create<vector::BitCastOp>(loc, vectorI8x20, abcde);
    abcde = rewriter.create<vector::ShuffleOp>(loc, abcde, abcde, inv20);
    rewriter.create<vector::StoreOp>(loc, abcde, ciphertext, c0);

    // rewriter.create<vector::PrintOp>(loc, abcde);
    rewriter.eraseOp(op);
    return success();
  }
};

class Sha1LoweringWithoutSHA1ISAPattern : public OpRewritePattern<crypto::Sha1_OP> {
public:
  using OpRewritePattern<crypto::Sha1_OP>::OpRewritePattern;

  LogicalResult matchAndRewrite(crypto::Sha1_OP op,
                                PatternRewriter &rewriter) const override {
    CHECK_ATTR(op)
    auto loc = op->getLoc();
    Value message = op->getOperand(0);
    Value message_len = op->getOperand(1);
    Value ciphertext = op->getOperand(2);

    Value origin_len = message_len;
    Value sixtyfour_I32 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(64));
    Value eight_I32 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(8));
    Value one_I32 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(1));
    Value one_I8 = rewriter.create<ConstantOp>(loc, rewriter.getI8Type(), rewriter.getI8IntegerAttr(0x80));
    Value zero_I8 = rewriter.create<ConstantOp>(loc, rewriter.getI8Type(), rewriter.getI8IntegerAttr(0));

    // Value one_I8_index = rewriter.create<ConstantOp>(loc, rewriter.getI8Type(), rewriter.getI8IntegerAttr(1));
    // Value two_I8 = rewriter.create<ConstantOp>(loc, rewriter.getI8Type(), rewriter.getI8IntegerAttr(2));
    // Value three_I8 = rewriter.create<ConstantOp>(loc, rewriter.getI8Type(), rewriter.getI8IntegerAttr(3));

    Value c0 = rewriter.create<ConstantIndexOp>(loc, 0);
    Value c2 = rewriter.create<ConstantIndexOp>(loc, 2);
    Value c3 = rewriter.create<ConstantIndexOp>(loc, 3);
    Value c16 = rewriter.create<ConstantIndexOp>(loc, 16);
    Value c64 = rewriter.create<ConstantIndexOp>(loc, 64);

    Value c31_I32 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(31));
    Value c30_I32 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(30));
    Value c27_I32 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(27));
    Value c5_I32 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(5));
    Value c2_I32 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(2));
    // Value c1_I32 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(1));
    Value c0_I32 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0));
    Value c64_i32 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(64));
    
    Value padded_message_len = rewriter.create<arith::AddIOp>(loc, c64_i32, message_len);
    padded_message_len = rewriter.create<arith::IndexCastOp>(loc, rewriter.getIndexType(), padded_message_len);
    auto memrefType = MemRefType::get({mlir::ShapedType::kDynamic}, rewriter.getI8Type());
    Value padded_message = rewriter.create<memref::AllocaOp>(loc, memrefType, ValueRange{padded_message_len});

    

    Value num_zeros =
                rewriter.create<arith::SubIOp>(loc, sixtyfour_I32,
                rewriter.create<arith::RemSIOp>(loc,
                rewriter.create<arith::AddIOp>(loc, origin_len, eight_I32), sixtyfour_I32));
    Value cond = rewriter.create<arith::CmpIOp>(loc, arith::CmpIPredicate::slt, num_zeros, one_I32);
    num_zeros = rewriter.create<SelectOp>(loc, cond, rewriter.create<AddIOp>(loc, num_zeros, one_I32), num_zeros);

    Value padded_len = rewriter.create<arith::AddIOp>(loc,
                      rewriter.create<arith::AddIOp>(loc, origin_len, num_zeros), eight_I32);
    Value padded_length = padded_len;
    padded_len = rewriter.create<arith::IndexCastOp>(loc, rewriter.getIndexType(), padded_len);
    rewriter.create<memref::CopyOp>(loc, message, padded_message);
    origin_len = rewriter.create<arith::IndexCastOp>(loc, rewriter.getIndexType(), origin_len);
    rewriter.create<memref::StoreOp>(loc, one_I8, padded_message, origin_len);

    Value c1 = rewriter.create<ConstantIndexOp>(loc, 1);
    num_zeros = rewriter.create<arith::IndexCastOp>(loc, rewriter.getIndexType(), num_zeros);

    Value start_loop = rewriter.create<arith::AddIOp>(loc, origin_len, c1);
    Value end_loop = rewriter.create<arith::AddIOp>(loc, num_zeros, start_loop);

    rewriter.create<scf::ForOp>(
      loc, start_loop, end_loop, c1, ValueRange{},
      [&](OpBuilder &builder, Location loc, Value ivs, ValueRange iargs){
        rewriter.create<memref::StoreOp>(loc, zero_I8, padded_message, ivs);
        rewriter.create<scf::YieldOp>(loc);
      });

    Value eight_I64 = rewriter.create<ConstantOp>(loc, rewriter.getI64Type(), rewriter.getI64IntegerAttr(8));
    Value c8 = rewriter.create<ConstantIndexOp>(loc, 8);

    message_len = rewriter.create<arith::ExtUIOp>(loc, rewriter.getI64Type(), message_len);
    message_len = rewriter.create<arith::MulIOp>(loc, message_len, eight_I64);
    Value message_len_v = rewriter.create<vector::BroadcastOp>(loc, VectorType::get({1}, rewriter.getI64Type()), message_len);
    message_len_v = rewriter.create<vector::BitCastOp>(loc, VectorType::get({8}, rewriter.getI8Type()), message_len_v);
    SmallVector<int64_t> inv8{7,6,5,4,3,2,1,0};
    message_len_v = rewriter.create<vector::ShuffleOp>(loc, message_len_v, message_len_v, inv8);
    Value pos =  rewriter.create<arith::SubIOp>(loc, padded_len, c8); 
    rewriter.create<vector::StoreOp>(loc, message_len_v, padded_message, pos);

    Value cN = rewriter.create<arith::IndexCastOp>(loc, rewriter.getIndexType(), padded_length);

    // Initialization
    SmallVector<int> abcd_inverse = {
                                271733878/*0x10325476*/,
                                -1732584194/*0x98BADCFE*/,
                                -271733879/*0xEFCDAB89*/,
                                1732584193 /*0x67452301*/};
    SmallVector<int> e_inverse = {0, 0, 0, -1009589776 /*0xC3D2E1F0*/};
    Value abcd_input = rewriter.create<ConstantOp>(loc, rewriter.getI32VectorAttr(abcd_inverse));
    Value e_input = rewriter.create<ConstantOp>(loc, rewriter.getI32VectorAttr(e_inverse));

    SmallVector<int64_t> inv16{15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0};
    VectorType vectorI8x16 = VectorType::get({16}, rewriter.getI8Type());
    VectorType vectorI32x4 = VectorType::get({4}, rewriter.getI32Type());

    scf::ForOp forOp = rewriter.create<scf::ForOp>(
      loc, c0, cN, c64, ValueRange{abcd_input, e_input},
      [&](OpBuilder &builder, Location loc, Value ivs, ValueRange iargs){
        Value abcd = iargs[0], e = iargs[1];
        Value idx = ivs;

        // Save the original hash value
        Value abcd_0 = abcd;
        Value e_0 = e;

        // Rounds 0-3
        Value input0 = rewriter.create<vector::LoadOp>(loc, vectorI8x16, padded_message, idx);
        input0 = rewriter.create<vector::ShuffleOp>(loc, input0, input0, inv16);
        Value data_0 = rewriter.create<vector::BitCastOp>(loc, vectorI32x4, input0);
        e = rewriter.create<arith::AddIOp>(loc, e, data_0);
        Value e_1 = abcd;
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e, c0);

        // Rounds 4-7
        idx = rewriter.create<arith::AddIOp>(loc, idx, c16);
        Value input1 = rewriter.create<vector::LoadOp>(loc, vectorI8x16, padded_message, idx);
        input1 = rewriter.create<vector::ShuffleOp>(loc, input1, input1, inv16);
        Value data_1 = rewriter.create<vector::BitCastOp>(loc, vectorI32x4, input1);
        e_1 = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e_1, data_1);
        e = abcd;
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e_1, c0);
        data_0 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_0, data_1);

        // Rounds 8-11
        idx = rewriter.create<arith::AddIOp>(loc, idx, c16);
        Value input2 = rewriter.create<vector::LoadOp>(loc, vectorI8x16, padded_message, idx);
        input2 = rewriter.create<vector::ShuffleOp>(loc, input2, input2, inv16);
        Value data_2 = rewriter.create<vector::BitCastOp>(loc, vectorI32x4, input2);
        e = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e, data_2);
        e_1 = abcd;
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e, c0);
        data_1 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_1, data_2);
        data_0 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_0, data_2);

        // Rounds 12-15
        idx = rewriter.create<arith::AddIOp>(loc, idx, c16);
        Value input3 = rewriter.create<vector::LoadOp>(loc, vectorI8x16, padded_message, idx);
        input3 = rewriter.create<vector::ShuffleOp>(loc, input3, input3, inv16);
        Value data_3 = rewriter.create<vector::BitCastOp>(loc, vectorI32x4, input3);
        e_1 = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e_1, data_3);
        e = abcd;
        data_0 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, one_I32, data_0, data_3);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e_1, c0);
        data_2 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_2, data_3);
        data_1 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_1, data_3);

        // Rounds 16-19
        e = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e, data_0);
        e_1 = abcd;
        data_1 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, one_I32, data_1, data_0);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e, c0);
        data_3 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_3, data_0);
        data_2 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_2, data_0);

        // Rounds 20-23
        e_1 = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e_1, data_1);
        e = abcd;
        data_2 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, one_I32, data_2, data_1);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e_1, c1);
        data_0 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_0, data_1);
        data_3 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_3, data_1);

        // Rounds 24-27
        e = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e, data_2);
        e_1 = abcd;
        data_3 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, one_I32, data_3, data_2);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e, c1);
        data_1 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_1, data_2);
        data_0 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_0, data_2);

        // Rounds 28-31
        e_1 = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e_1, data_3);
        e = abcd;
        data_0 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, one_I32, data_0, data_3);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e_1, c1);
        data_2 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_2, data_3);
        data_1 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_1, data_3);

        // Rounds 32-35
        e = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e, data_0);
        e_1 = abcd;
        data_1 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, one_I32, data_1, data_0);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e, c1);
        data_3 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_3, data_0);
        data_2 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_2, data_0);

        // Rounds 36-39
        e_1 = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e_1, data_1);
        e = abcd;
        data_2 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, one_I32, data_2, data_1);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e_1, c1);
        data_0 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_0, data_1);
        data_3 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_3, data_1);

        // Rounds 40-43
        e = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e, data_2);
        e_1 = abcd;
        data_3 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, one_I32, data_3, data_2);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e, c2);
        data_1 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_1, data_2);
        data_0 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_0, data_2);

        // Rounds 44-47
        e_1 = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e_1, data_3);
        e = abcd;
        data_0 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, one_I32, data_0, data_3);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e_1, c2);
        data_2 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_2, data_3);
        data_1 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_1, data_3);

        // Rounds 48-51
        e = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e, data_0);
        e_1 = abcd;
        data_1 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, one_I32, data_1, data_0);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e, c2);
        data_3 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_3, data_0);
        data_2 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_2, data_0);

        // Rounds 52-55
        e_1 = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e_1, data_1);
        e = abcd;
        data_2 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, one_I32, data_2, data_1);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e_1, c2);
        data_0 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_0, data_1);
        data_3 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_3, data_1);

        // Rounds 56-59
        e = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e, data_2);
        e_1 = abcd;
        data_3 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, one_I32, data_3, data_2);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e, c2);
        data_1 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_1, data_2);
        data_0 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_0, data_2);

        // Rounds 60-63
        e_1 = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e_1, data_3);
        e = abcd;
        data_0 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, one_I32, data_0, data_3);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e_1, c3);
        data_2 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_2, data_3);
        data_1 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_1, data_3);

        // Rounds 64-67
        e = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e, data_0);
        e_1 = abcd;
        data_1 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, one_I32, data_1, data_0);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e, c3);
        data_3 = getCustomSHA1msg1Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, data_3, data_0);
        data_2 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_2, data_0);

        // Rounds 68-71
        e_1 = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e_1, data_1);
        e = abcd;
        data_2 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, one_I32, data_2, data_1);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e_1, c3);
        data_3 = builder.create<arith::XOrIOp>(loc, vectorI32x4, data_3, data_1);

        // Rounds 72-75
        e = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e, data_2);
        e_1 = abcd;
        data_3 = getCustomSHA1msg2Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c31_I32, one_I32, data_3, data_2);
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e, c3);

        // Rounds 76-79
        e_1 = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e_1, data_3);
        e = abcd;
        abcd = getCustomSHA1rnds4Op(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c2_I32, c5_I32, c27_I32, c30_I32, abcd, e_1, c3);

        // Final
        e = getCustomSHA1nexteOp(loc, rewriter, vectorI32x4, c0, c1, c2, c3, c0_I32, c2_I32, c30_I32, e, e_0);
        abcd = builder.create<arith::AddIOp>(loc, abcd, abcd_0);

        builder.create<scf::YieldOp>(loc, std::vector<Value>{abcd, e});
      }
    );

    // Save the final hash value

    VectorType vectorI8x20 = VectorType::get({20}, rewriter.getI8Type());
    VectorType vectorI32x5 = VectorType::get({5}, rewriter.getI32Type());

    Value abcd = forOp.getResults()[0];
    Value e = forOp.getResults()[1];
    SmallVector<int64_t> inv4{3,2,1,0};
    abcd = rewriter.create<vector::ShuffleOp>(loc, abcd, abcd, inv4);

    Value e_3 = rewriter.create<vector::ExtractElementOp>(loc, e, c3);
    Value abcde = rewriter.create<vector::SplatOp>(loc, e_3, vectorI32x5);
    abcde = rewriter.create<vector::InsertOp>(loc, rewriter.create<vector::ExtractElementOp>(loc, abcd, c0), abcde, c0);
    abcde = rewriter.create<vector::InsertOp>(loc, rewriter.create<vector::ExtractElementOp>(loc, abcd, c1), abcde, c1);
    abcde = rewriter.create<vector::InsertOp>(loc, rewriter.create<vector::ExtractElementOp>(loc, abcd, c2), abcde, c2);
    abcde = rewriter.create<vector::InsertOp>(loc, rewriter.create<vector::ExtractElementOp>(loc, abcd, c3), abcde, c3);

    SmallVector<int64_t> inv20{3,2,1,0,7,6,5,4,11,10,9,8,15,14,13,12,19,18,17,16};
    abcde = rewriter.create<vector::BitCastOp>(loc, vectorI8x20, abcde);
    abcde = rewriter.create<vector::ShuffleOp>(loc, abcde, abcde, inv20);
    rewriter.create<vector::StoreOp>(loc, abcde, ciphertext, c0);

    rewriter.eraseOp(op);
    return success();
  }
};

class CmpOpConversion : public OpRewritePattern<crypto::CmpOp> {
public:
  using OpRewritePattern<crypto::CmpOp>::OpRewritePattern;

  LogicalResult matchAndRewrite(crypto::CmpOp op,
                                PatternRewriter &rewriter) const override {
    auto loc = op->getLoc();
    Value mem0 = op->getOperand(0);
    Value mem1 = op->getOperand(1);

    // the dims of two memrefs
    Value dim0 = rewriter.create<memref::DimOp>(loc, /*source=*/mem0, /*index=*/0);
    Value dim1 = rewriter.create<memref::DimOp>(loc, /*source=*/mem1, /*index=*/0);
    // compare the dims
    Value isEqual = rewriter.create<arith::CmpIOp>(loc, /*predicate=*/arith::CmpIPredicate::eq, /*lhs=*/dim0, /*rhs=*/dim1);

    auto ifOp = rewriter.create<scf::IfOp>(loc, /*cond=*/isEqual, /*thenBuilder=*/[&](OpBuilder &builder, Location loc) {
      Value isSame = rewriter.create<ConstantOp>(loc, 
        /*result=*/rewriter.getI1Type(),
        /*value=*/rewriter.getIntegerAttr(/*type=*/rewriter.getI1Type(), APInt(1, 1))
      );
      Value c0 = rewriter.create<ConstantIndexOp>(loc, /*value=*/0);

      // compare the elements
      auto whileOp = rewriter.create<scf::WhileOp>(loc, /*resultTypes=*/TypeRange{rewriter.getIndexType(), rewriter.getI1Type()},
      /*operands=*/ValueRange{c0, isSame}, /*beforeBuilder=*/[&](OpBuilder &builder, Location loc, ValueRange args) {
        Value idx = args[0];
        // check if the induceVar is within the index range
        Value cond = builder.create<arith::CmpIOp>(loc, /*predicate=*/arith::CmpIPredicate::ult, /*lhs=*/idx, /*rhs=*/dim0);
        // elements must be equal till now
        cond = builder.create<arith::AndIOp>(loc, /*lhs=*/cond, /*rhs=*/args[1]);
        builder.create<scf::ConditionOp>(loc, /*condition=*/cond, /*args=*/args);
      }, /*afterBuilder=*/[&](OpBuilder &builder, Location loc, ValueRange args) {
        Value idx = args[0];
        Value ele0 = builder.create<memref::LoadOp>(loc, /*memref=*/mem0, /*indices=*/ValueRange{idx});
        Value ele1 = builder.create<memref::LoadOp>(loc, /*memref=*/mem1, /*indices=*/ValueRange{idx});
        // compare the elements
        Value cond = builder.create<arith::CmpIOp>(loc, /*predicate=*/arith::CmpIPredicate::eq, /*lhs=*/ele0, /*rhs=*/ele1);
        // increment the induceVar
        idx = builder.create<arith::AddIOp>(loc, /*lhs=*/idx, /*rhs=*/rewriter.create<arith::ConstantIndexOp>(loc, /*value=*/1));
        
        builder.create<scf::YieldOp>(loc, ValueRange{idx, cond});
      });
      rewriter.create<scf::YieldOp>(loc, /*results=*/ValueRange{whileOp.getResult(1)});
    }, 
    /*elseBuilder=*/[&](OpBuilder &builder, Location loc) {
      // if the dims are not equal, return false
      Value falseVal = rewriter.create<ConstantOp>(loc, 
        /*result=*/rewriter.getI1Type(),
        /*value=*/rewriter.getIntegerAttr(/*type=*/rewriter.getI1Type(), APInt(1, 0)) 
      );
      rewriter.create<scf::YieldOp>(loc, /*results=*/ValueRange{falseVal});
    });

    rewriter.replaceOp(op, ValueRange{ifOp.getResult(0)});
    return success();
  }

};

} // namespace

namespace {

#define X_LEN 16

Value RTL(Location loc, PatternRewriter &rewriter, Value num, int32_t n) {
  Value RTL_l = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(n));
  Value RTL_r = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(32-n));
  return rewriter.create<arith::OrIOp>(loc, 
          rewriter.create<arith::ShLIOp>(loc, num, RTL_l),
          rewriter.create<arith::ShRUIOp>(loc, num, RTL_r));
}

Value MD5FFOp(Location loc, PatternRewriter &rewriter, Value a, Value b, Value c, Value d, Value x, int32_t s, Value ac) {
  Value fullOneI32 = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(-1));

  Value bandc = rewriter.create<arith::AndIOp>(loc, b, c);
  Value notb = rewriter.create<arith::XOrIOp>(loc, b, fullOneI32);
  Value notbandd = rewriter.create<arith::AndIOp>(loc, notb, d);
  Value F_value = rewriter.create<arith::OrIOp>(loc, bandc, notbandd);
  Value result = rewriter.create<arith::AddIOp>(loc, 
                rewriter.create<arith::AddIOp>(loc, 
                  rewriter.create<arith::AddIOp>(loc, F_value, x), 
                  ac),
                a);
  result = RTL(loc, rewriter, result, s);
  result = rewriter.create<arith::AddIOp>(loc, result, b);
  return result;
}

Value MD5GGOp(Location loc, PatternRewriter &rewriter, Value a, Value b, Value c, Value d, Value x, int32_t s, Value ac) {
  Value fullOneI32 = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(-1));

  Value bandd = rewriter.create<arith::AndIOp>(loc, b, d);
  Value notd = rewriter.create<arith::XOrIOp>(loc, d, fullOneI32);
  Value candnotd = rewriter.create<arith::AndIOp>(loc, c, notd);
  Value G_value = rewriter.create<arith::OrIOp>(loc, bandd, candnotd);
  Value result = rewriter.create<arith::AddIOp>(loc, 
                rewriter.create<arith::AddIOp>(loc, 
                  rewriter.create<arith::AddIOp>(loc, G_value, x), 
                  ac),
                a);
  result = RTL(loc, rewriter, result, s);
  result = rewriter.create<arith::AddIOp>(loc, result, b);
  return result;
}

Value MD5HHOp(Location loc, PatternRewriter &rewriter, Value a, Value b, Value c, Value d, Value x, int32_t s, Value ac) {
  Value bxorc = rewriter.create<arith::XOrIOp>(loc, b, c);
  Value H_value = rewriter.create<arith::XOrIOp>(loc, bxorc, d);
  Value result = rewriter.create<arith::AddIOp>(loc, 
                rewriter.create<arith::AddIOp>(loc, 
                  rewriter.create<arith::AddIOp>(loc, H_value, x), 
                  ac),
                a);
  result = RTL(loc, rewriter, result, s);
  result = rewriter.create<arith::AddIOp>(loc, result, b);
  return result;
}

Value MD5IIOp(Location loc, PatternRewriter &rewriter, Value a, Value b, Value c, Value d, Value x, int32_t s, Value ac) {
  Value fullOneI32 = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(-1));
  
  Value notd = rewriter.create<arith::XOrIOp>(loc, d, fullOneI32);
  Value bornotd = rewriter.create<arith::OrIOp>(loc, b, notd);
  Value I_value = rewriter.create<arith::XOrIOp>(loc, c, bornotd);
  Value result = rewriter.create<arith::AddIOp>(loc, 
                rewriter.create<arith::AddIOp>(loc, 
                  rewriter.create<arith::AddIOp>(loc, I_value, x), 
                  ac),
                a);
  result = RTL(loc, rewriter, result, s);
  result = rewriter.create<arith::AddIOp>(loc, result, b);
  return result;
}


class MD5LoweringX86Pattern : public OpRewritePattern<crypto::MD5OP> {
public:
  using OpRewritePattern<crypto::MD5OP>::OpRewritePattern;

  LogicalResult matchAndRewrite(crypto::MD5OP op,
                                PatternRewriter &rewriter) const override {
    CHECK_ATTR(op)
    auto loc = op->getLoc();
    Value message = op->getOperand(0);
    Value messageLen = op->getOperand(1);
    Value ciphertext = op->getOperand(2);

    // prepare some types
    VectorType vectorI8x16 = VectorType::get({16}, rewriter.getI8Type());
    VectorType vectorI8x64 = VectorType::get({64}, rewriter.getI8Type());
    VectorType vectorI1x64 = VectorType::get({64}, rewriter.getI1Type());
    VectorType vectorI32x16 = VectorType::get({16}, rewriter.getI32Type());
    VectorType vectorI64x1 = VectorType::get({1}, rewriter.getI64Type());
    VectorType vectorI8x8 = VectorType::get({8}, rewriter.getI8Type());

    Value c64I32 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(64));
    Value c8I32 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(8));
    Value c0I8 = rewriter.create<ConstantOp>(loc, rewriter.getI8Type(), rewriter.getI8IntegerAttr(0));
    Value c_128I8 = rewriter.create<ConstantOp>(loc, rewriter.getI8Type(), rewriter.getI8IntegerAttr(0x80));
    Value c8I64 = rewriter.create<ConstantOp>(loc, rewriter.getI64Type(), rewriter.getI64IntegerAttr(8));
    Value c0 = rewriter.create<ConstantIndexOp>(loc, 0);
    Value c1 = rewriter.create<ConstantIndexOp>(loc, 1);
    Value c2 = rewriter.create<ConstantIndexOp>(loc, 2);
    Value c8 = rewriter.create<ConstantIndexOp>(loc, 8);
    Value c56 = rewriter.create<ConstantIndexOp>(loc, 56);
    Value c64 = rewriter.create<ConstantIndexOp>(loc, 64);
    Value cn = rewriter.create<arith::IndexCastOp>(loc, rewriter.getIndexType(), messageLen);

    Value indexes[X_LEN];
    for (int i = 0; i < X_LEN; i++) {
      indexes[i] = rewriter.create<arith::ConstantIndexOp>(loc, i);
    }

    // s(RTL) value in rounds
    int32_t FFs1 = 7;
    int32_t FFs2 = 12;
    int32_t FFs3 = 17;
    int32_t FFs4 = 22;

    int32_t GGs1 = 5;
    int32_t GGs2 = 9;
    int32_t GGs3 = 14;
    int32_t GGs4 = 20;

    int32_t HHs1 = 4;
    int32_t HHs2 = 11;
    int32_t HHs3 = 16;
    int32_t HHs4 = 23;

    int32_t IIs1 = 6;
    int32_t IIs2 = 10;
    int32_t IIs3 = 15;
    int32_t IIs4 = 21;

    // ac value in rounds
    Value FFac1 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xd76aa478));
    Value FFac2 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xe8c7b756));
    Value FFac3 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x242070db));
    Value FFac4 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xc1bdceee));
    Value FFac5 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xf57c0faf));
    Value FFac6 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x4787c62a));
    Value FFac7 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xa8304613));
    Value FFac8 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xfd469501));
    Value FFac9 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x698098d8));
    Value FFac10 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x8b44f7af));
    Value FFac11 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xffff5bb1));
    Value FFac12 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x895cd7be));
    Value FFac13 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x6b901122));
    Value FFac14 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xfd987193));
    Value FFac15 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xa679438e));
    Value FFac16 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x49b40821));

    Value GGac1 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xf61e2562));
    Value GGac2 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xc040b340));
    Value GGac3 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x265e5a51));
    Value GGac4 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xe9b6c7aa));
    Value GGac5 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xd62f105d));
    Value GGac6 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x2441453));
    Value GGac7 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xd8a1e681));
    Value GGac8 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xe7d3fbc8));
    Value GGac9 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x21e1cde6));
    Value GGac10 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xc33707d6));
    Value GGac11 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xf4d50d87));
    Value GGac12 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x455a14ed));
    Value GGac13 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xa9e3e905));
    Value GGac14 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xfcefa3f8));
    Value GGac15 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x676f02d9));
    Value GGac16 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x8d2a4c8a));

    Value HHac1 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xfffa3942));
    Value HHac2 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x8771f681));
    Value HHac3 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x6d9d6122));
    Value HHac4 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xfde5380c));
    Value HHac5 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xa4beea44));
    Value HHac6 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x4bdecfa9));
    Value HHac7 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xf6bb4b60));
    Value HHac8 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xbebfbc70));
    Value HHac9 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x289b7ec6));
    Value HHac10 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xeaa127fa));
    Value HHac11 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xd4ef3085));
    Value HHac12 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x4881d05));
    Value HHac13 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xd9d4d039));
    Value HHac14 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xe6db99e5));
    Value HHac15 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x1fa27cf8));
    Value HHac16 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xc4ac5665));

    Value IIac1 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xf4292244));
    Value IIac2 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x432aff97));
    Value IIac3 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xab9423a7));
    Value IIac4 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xfc93a039));
    Value IIac5 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x655b59c3));
    Value IIac6 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x8f0ccc92));
    Value IIac7 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xffeff47d));
    Value IIac8 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x85845dd1));
    Value IIac9 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x6fa87e4f));
    Value IIac10 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xfe2ce6e0));
    Value IIac11 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xa3014314));
    Value IIac12 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x4e0811a1));
    Value IIac13 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xf7537e82));
    Value IIac14 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xbd3af235));
    Value IIac15 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x2ad7d2bb));
    Value IIac16 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xeb86d391));

    // calculate the padding size
    Value rem = rewriter.create<arith::RemSIOp>(loc, /*lhs=*/messageLen, /*rhs=*/c64I32);
    Value firstOneOffset = rewriter.create<arith::IndexCastOp>(loc, rewriter.getIndexType(), rem);
    Value diff = rewriter.create<arith::SubIOp>(loc, /*lhs=*/c64I32, /*rhs=*/rem);
    Value isPadTwo = rewriter.create<arith::CmpIOp>(loc, /*predicate=*/arith::CmpIPredicate::sle, /*lhs=*/diff, /*rhs=*/c8I32);
    Value padSize1 = rewriter.create<arith::AddIOp>(loc, /*lhs=*/messageLen, /*rhs=*/diff);
    Value padSize2 = rewriter.create<arith::AddIOp>(loc, /*lhs=*/padSize1, /*rhs=*/c64I32);
    Value padSize = rewriter.create<arith::SelectOp>(loc, /*condition=*/isPadTwo, /*true_value=*/padSize2, /*false_value=*/padSize1);
    Value padSizeIdx = rewriter.create<arith::IndexCastOp>(loc, rewriter.getIndexType(), padSize);
    Value firstPaddingBlockIdx = rewriter.create<arith::DivUIOp>(loc, /*lhs=*/cn, /*rhs=*/c64);
    Value blockNum = rewriter.create<arith::DivUIOp>(loc, /*lhs=*/padSizeIdx, /*rhs=*/c64);
    Value lastPaddingBlockIdx = rewriter.create<arith::SubIOp>(loc, /*lhs=*/blockNum, /*rhs=*/c1);


    // Initialization
    Value H0 = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x67452301));  
    Value H1 = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0xEFCDAB89));  
    Value H2 = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x98BADCFE)); 
    Value H3 = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0x10325476));   


    scf::ForOp forOp = rewriter.create<scf::ForOp>(
      loc, c0, padSizeIdx, c64, ValueRange{H0, H1, H2, H3},
      [&](OpBuilder &builder, Location loc, Value ivs, ValueRange iargs){
        Value idx = ivs;
        Value a = iargs[0], b = iargs[1], c = iargs[2], d = iargs[3];
        Value H0_last = iargs[0], H1_last = iargs[1], H2_last = iargs[2], H3_last = iargs[3];

        // decide whether or not to do the padding
        Value curBlockIdx = rewriter.create<arith::DivUIOp>(loc, /*lhs=*/idx, /*rhs=*/c64);
        Value isPaddingNow = rewriter.create<arith::CmpIOp>(loc, /*predicate=*/arith::CmpIPredicate::eq, /*lhs=*/curBlockIdx, /*rhs=*/firstPaddingBlockIdx);
        Value isLastBlock = rewriter.create<arith::CmpIOp>(loc, /*predicate=*/arith::CmpIPredicate::eq, /*lhs=*/curBlockIdx, /*rhs=*/lastPaddingBlockIdx);
        scf::IfOp ifPaddingOp = rewriter.create<scf::IfOp>(
          loc, /*cond=*/isPaddingNow, /*thenBuilder=*/[&](OpBuilder &builder, Location loc) {
            Value loadingMask = rewriter.create<vector::CreateMaskOp>(loc, /*resultType0=*/vectorI1x64, /*operand=*/ValueRange{firstOneOffset});
            // load W[16] for each iteration
            Value zeroBlock = rewriter.create<vector::SplatOp>(loc, /*aggregate=*/vectorI8x64, /*input=*/ValueRange{c0I8});
            Value loadedData = rewriter.create<vector::MaskedLoadOp>(loc, /*result=*/vectorI8x64, /*base=*/message, /*indices=*/idx, /*mask=*/loadingMask, /*pass_thru=*/zeroBlock);
            Value paddedBlock = rewriter.create<vector::InsertElementOp>(loc, /*source=*/c_128I8, /*dest=*/loadedData, /*position=*/firstOneOffset);
            rewriter.create<scf::YieldOp>(loc, /*results=*/ValueRange{paddedBlock});
          },
          /*elseBuilder=*/[&](OpBuilder &builder, Location loc) {
            Value zeroBlock = rewriter.create<vector::SplatOp>(loc, /*aggregate=*/vectorI8x64, /*input=*/ValueRange{c0I8});
            Value loadedData = rewriter.create<vector::LoadOp>(loc, /*result=*/vectorI8x64, /*base=*/message, /*indices=*/idx);
            Value result = rewriter.create<arith::SelectOp>(loc, /*condition=*/isLastBlock, /*true_value=*/zeroBlock, /*false_value=*/loadedData);
            rewriter.create<scf::YieldOp>(loc, /*results=*/ValueRange{result});
          });
          scf::IfOp ifLastBlockOp = rewriter.create<scf::IfOp>(
          loc, /*cond=*/isLastBlock, /*thenBuilder=*/[&](OpBuilder &builder, Location loc) {
            Value extLen = rewriter.create<arith::ExtUIOp>(loc, /*out=*/rewriter.getI64Type(), /*in=*/messageLen);
            Value msgBits = rewriter.create<arith::MulIOp>(loc, /*lhs=*/extLen, /*rhs=*/c8I64);
            Value t0 = rewriter.create<vector::BroadcastOp>(loc, /*vector=*/vectorI64x1, /*source=*/msgBits);
            Value t1 = rewriter.create<vector::BitCastOp>(loc, /*result=*/vectorI8x8, /*source=*/t0);
            scf::ForOp innerForOp = rewriter.create<scf::ForOp>(
              loc, /*lowerBound=*/c0, /*upperBound=*/c8, /*step=*/c1, /*iterArgs=*/ValueRange{ifPaddingOp.getResults()[0]},
              /*odsArg4=*/[&](OpBuilder &builder, Location loc, Value ivs, ValueRange iargs) {
                Value tailIdx = rewriter.create<arith::AddIOp>(loc, /*lhs=*/ivs, /*rhs=*/c56);
                Value t3 = rewriter.create<vector::ExtractElementOp>(loc, /*source=*/t1, /*position=*/ivs);
                Value t4 = rewriter.create<vector::InsertElementOp>(loc, /*source=*/t3, /*dest=*/iargs[0], /*position=*/tailIdx);
                rewriter.create<scf::YieldOp>(loc, /*results=*/ValueRange{t4});
              });
            rewriter.create<scf::YieldOp>(loc, /*results=*/ValueRange{innerForOp.getResults()[0]});
          },
          /*elseBuilder=*/[&](OpBuilder &builder, Location loc) {
            rewriter.create<scf::YieldOp>(loc, /*results=*/ValueRange{ifPaddingOp.getResults()[0]});
          });

        // X[16]
        Value X[X_LEN];
        Value input = ifLastBlockOp.getResults()[0];
        input = rewriter.create<vector::BitCastOp>(loc, vectorI32x16, input);
        for (int i = 0; i < 16; i++) {
            X[i] = rewriter.create<vector::ExtractElementOp>(loc, input, indexes[i]);
        }

        // FF 16 rounds
        a = MD5FFOp(loc, rewriter, a, b, c, d, X[0], FFs1, FFac1);
        d = MD5FFOp(loc, rewriter, d, a, b, c, X[1], FFs2, FFac2);
        c = MD5FFOp(loc, rewriter, c, d, a, b, X[2], FFs3, FFac3);
        b = MD5FFOp(loc, rewriter, b, c, d, a, X[3], FFs4, FFac4);
        a = MD5FFOp(loc, rewriter, a, b, c, d, X[4], FFs1, FFac5);
        d = MD5FFOp(loc, rewriter, d, a, b, c, X[5], FFs2, FFac6);
        c = MD5FFOp(loc, rewriter, c, d, a, b, X[6], FFs3, FFac7);
        b = MD5FFOp(loc, rewriter, b, c, d, a, X[7], FFs4, FFac8);
        a = MD5FFOp(loc, rewriter, a, b, c, d, X[8], FFs1, FFac9);
        d = MD5FFOp(loc, rewriter, d, a, b, c, X[9], FFs2, FFac10);
        c = MD5FFOp(loc, rewriter, c, d, a, b, X[10], FFs3, FFac11);
        b = MD5FFOp(loc, rewriter, b, c, d, a, X[11], FFs4, FFac12);
        a = MD5FFOp(loc, rewriter, a, b, c, d, X[12], FFs1, FFac13);
        d = MD5FFOp(loc, rewriter, d, a, b, c, X[13], FFs2, FFac14);
        c = MD5FFOp(loc, rewriter, c, d, a, b, X[14], FFs3, FFac15);
        b = MD5FFOp(loc, rewriter, b, c, d, a, X[15], FFs4, FFac16);

        // GG 16 rounds
        a = MD5GGOp(loc, rewriter, a, b, c, d, X[1], GGs1, GGac1);
        d = MD5GGOp(loc, rewriter, d, a, b, c, X[6], GGs2, GGac2);
        c = MD5GGOp(loc, rewriter, c, d, a, b, X[11], GGs3, GGac3);
        b = MD5GGOp(loc, rewriter, b, c, d, a, X[0], GGs4, GGac4);
        a = MD5GGOp(loc, rewriter, a, b, c, d, X[5], GGs1, GGac5);
        d = MD5GGOp(loc, rewriter, d, a, b, c, X[10], GGs2, GGac6);
        c = MD5GGOp(loc, rewriter, c, d, a, b, X[15], GGs3, GGac7);
        b = MD5GGOp(loc, rewriter, b, c, d, a, X[4], GGs4, GGac8);
        a = MD5GGOp(loc, rewriter, a, b, c, d, X[9], GGs1, GGac9);
        d = MD5GGOp(loc, rewriter, d, a, b, c, X[14], GGs2, GGac10);
        c = MD5GGOp(loc, rewriter, c, d, a, b, X[3], GGs3, GGac11);
        b = MD5GGOp(loc, rewriter, b, c, d, a, X[8], GGs4, GGac12);
        a = MD5GGOp(loc, rewriter, a, b, c, d, X[13], GGs1, GGac13);
        d = MD5GGOp(loc, rewriter, d, a, b, c, X[2], GGs2, GGac14);
        c = MD5GGOp(loc, rewriter, c, d, a, b, X[7], GGs3, GGac15);
        b = MD5GGOp(loc, rewriter, b, c, d, a, X[12], GGs4, GGac16);

        // HH 16 rounds
        a = MD5HHOp(loc, rewriter, a, b, c, d, X[5], HHs1, HHac1);
        d = MD5HHOp(loc, rewriter, d, a, b, c, X[8], HHs2, HHac2);
        c = MD5HHOp(loc, rewriter, c, d, a, b, X[11], HHs3, HHac3);
        b = MD5HHOp(loc, rewriter, b, c, d, a, X[14], HHs4, HHac4);
        a = MD5HHOp(loc, rewriter, a, b, c, d, X[1], HHs1, HHac5);
        d = MD5HHOp(loc, rewriter, d, a, b, c, X[4], HHs2, HHac6);
        c = MD5HHOp(loc, rewriter, c, d, a, b, X[7], HHs3, HHac7);
        b = MD5HHOp(loc, rewriter, b, c, d, a, X[10], HHs4, HHac8);
        a = MD5HHOp(loc, rewriter, a, b, c, d, X[13], HHs1, HHac9);
        d = MD5HHOp(loc, rewriter, d, a, b, c, X[0], HHs2, HHac10);
        c = MD5HHOp(loc, rewriter, c, d, a, b, X[3], HHs3, HHac11);
        b = MD5HHOp(loc, rewriter, b, c, d, a, X[6], HHs4, HHac12);
        a = MD5HHOp(loc, rewriter, a, b, c, d, X[9], HHs1, HHac13);
        d = MD5HHOp(loc, rewriter, d, a, b, c, X[12], HHs2, HHac14);
        c = MD5HHOp(loc, rewriter, c, d, a, b, X[15], HHs3, HHac15);
        b = MD5HHOp(loc, rewriter, b, c, d, a, X[2], HHs4, HHac16);

        // II 16 rounds
        a = MD5IIOp(loc, rewriter, a, b, c, d, X[0], IIs1, IIac1);
        d = MD5IIOp(loc, rewriter, d, a, b, c, X[7], IIs2, IIac2);
        c = MD5IIOp(loc, rewriter, c, d, a, b, X[14], IIs3, IIac3);
        b = MD5IIOp(loc, rewriter, b, c, d, a, X[5], IIs4, IIac4);
        a = MD5IIOp(loc, rewriter, a, b, c, d, X[12], IIs1, IIac5);
        d = MD5IIOp(loc, rewriter, d, a, b, c, X[3], IIs2, IIac6);
        c = MD5IIOp(loc, rewriter, c, d, a, b, X[10], IIs3, IIac7);
        b = MD5IIOp(loc, rewriter, b, c, d, a, X[1], IIs4, IIac8);
        a = MD5IIOp(loc, rewriter, a, b, c, d, X[8], IIs1, IIac9);
        d = MD5IIOp(loc, rewriter, d, a, b, c, X[15], IIs2, IIac10);
        c = MD5IIOp(loc, rewriter, c, d, a, b, X[6], IIs3, IIac11);
        b = MD5IIOp(loc, rewriter, b, c, d, a, X[13], IIs4, IIac12);
        a = MD5IIOp(loc, rewriter, a, b, c, d, X[4], IIs1, IIac13);
        d = MD5IIOp(loc, rewriter, d, a, b, c, X[11], IIs2, IIac14);
        c = MD5IIOp(loc, rewriter, c, d, a, b, X[2], IIs3, IIac15);
        b = MD5IIOp(loc, rewriter, b, c, d, a, X[9], IIs4, IIac16);

        // H0[i] = H0[i-1] + a
        a = rewriter.create<arith::AddIOp>(loc, H0_last, a);
        // H1[i] = H1[i-1] + b
        b = rewriter.create<arith::AddIOp>(loc, H1_last, b);
        // H2[i] = H2[i-1] + c
        c = rewriter.create<arith::AddIOp>(loc, H2_last, c);
        // H3[i] = H3[i-1] + d
        d = rewriter.create<arith::AddIOp>(loc, H3_last, d);

        builder.create<scf::YieldOp>(loc, ValueRange{a, b, c, d});
      }
    );

    // Save the final hash value
    H0 = forOp.getResults()[0];
    H1 = forOp.getResults()[1];
    H2 = forOp.getResults()[2];
    H3 = forOp.getResults()[3];

    VectorType vectorI32x4 = VectorType::get({4}, rewriter.getI32Type());

    Value H0123 = rewriter.create<vector::SplatOp>(loc, H3, vectorI32x4);
    H0123 = rewriter.create<vector::InsertOp>(loc, H0, H0123, c0);
    H0123 = rewriter.create<vector::InsertOp>(loc, H1, H0123, c1);
    H0123 = rewriter.create<vector::InsertOp>(loc, H2, H0123, c2);

    H0123 = rewriter.create<vector::BitCastOp>(loc, vectorI8x16, H0123);
    rewriter.create<vector::StoreOp>(loc, H0123, ciphertext, c0);

    rewriter.eraseOp(op);
    return success();
  }
};

} // end anonymous namespace.

namespace {
#define W_LEN 68
#define W1_LEN 64

class SM3LoweringX86Pattern : public OpRewritePattern<crypto::SM3OP> {
 public:
  using OpRewritePattern<crypto::SM3OP>::OpRewritePattern;

  LogicalResult matchAndRewrite(crypto::SM3OP op,
                                PatternRewriter &rewriter) const override {
    CHECK_ATTR(op)
    auto loc = op.getLoc();
    Value plaintext = op->getOperand(0);
    Value msgLen = op->getOperand(1);
    Value ciphertext = op->getOperand(2);

    // Index args
    Value msgLenIdx = rewriter.create<IndexCastOp>(loc, rewriter.getIndexType(), msgLen);

    // Types
    VectorType vectorI8x64 = VectorType::get({64}, rewriter.getI8Type());
    VectorType vectorI8x8 = VectorType::get({8}, rewriter.getI8Type());
    VectorType vectorI1x64 = VectorType::get({64}, rewriter.getI1Type());
    VectorType vectorI64x1 = VectorType::get({1}, rewriter.getI64Type());
    VectorType vectorI32x16 = VectorType::get({16}, rewriter.getI32Type());
    VectorType vectorI8x32 = VectorType::get({32}, rewriter.getI8Type());
    VectorType vectorI32x8 = VectorType::get({8}, rewriter.getI32Type());

    // Constants
    Value indexes[W_LEN];
    for (int i = 0; i < W_LEN; i++) {
      indexes[i] = rewriter.create<arith::ConstantIndexOp>(loc, i);
    }

    Value c0 = rewriter.create<ConstantIndexOp>(loc, 0);
    Value c64 = rewriter.create<ConstantIndexOp>(loc, 64);

    Value H0 = rewriter.create<ConstantOp>(loc, rewriter.getI32IntegerAttr(0x7380166f));
    Value H1 = rewriter.create<ConstantOp>(loc, rewriter.getI32IntegerAttr(0x4914b2b9));
    Value H2 = rewriter.create<ConstantOp>(loc, rewriter.getI32IntegerAttr(0x172442d7));
    Value H3 = rewriter.create<ConstantOp>(loc, rewriter.getI32IntegerAttr(0xda8a0600));
    Value H4 = rewriter.create<ConstantOp>(loc, rewriter.getI32IntegerAttr(0xa96f30bc));
    Value H5 = rewriter.create<ConstantOp>(loc, rewriter.getI32IntegerAttr(0x163138aa));
    Value H6 = rewriter.create<ConstantOp>(loc, rewriter.getI32IntegerAttr(0xe38dee4d));
    Value H7 = rewriter.create<ConstantOp>(loc, rewriter.getI32IntegerAttr(0xb0fb0e4e));

    Value c8_I64 = rewriter.create<ConstantOp>(loc, rewriter.getI64IntegerAttr(8));
    Value c7_I32 = rewriter.create<ConstantOp>(loc, rewriter.getI32IntegerAttr(7));
    Value c8_I32 = rewriter.create<ConstantOp>(loc, rewriter.getI32IntegerAttr(8));
    Value c25_I32 = rewriter.create<ConstantOp>(loc, rewriter.getI32IntegerAttr(25));
    Value fullOneI32 = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32IntegerAttr(-1));
    Value c0I8 = rewriter.create<arith::ConstantOp>(loc, rewriter.getI8IntegerAttr(0));
    Value c_128I8 = rewriter.create<arith::ConstantOp>(loc, rewriter.getI8IntegerAttr(-128));
    Value c9_I32 = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32IntegerAttr(9));
    Value c12_I32 = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32IntegerAttr(12));
    Value c13_I32 = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32IntegerAttr(13));
    Value c15_I32 = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32IntegerAttr(15));
    Value c17_I32 = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32IntegerAttr(17));
    Value c19_I32 = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32IntegerAttr(19));
    Value c20_I32 = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32IntegerAttr(20));
    Value c23_I32 = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32IntegerAttr(23));
    Value c64_I32 = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32IntegerAttr(64));

    // calculate the padding size
    Value rem = rewriter.create<RemSIOp>(loc, msgLen, c64_I32);
    Value firstOneOffset = rewriter.create<IndexCastOp>(loc, rewriter.getIndexType(), rem);
    Value diff = rewriter.create<SubIOp>(loc, c64_I32, rem);

    Value isPadTwo = rewriter.create<CmpIOp>(loc, CmpIPredicate::sle, diff, c8_I32);
    Value padSize1 = rewriter.create<AddIOp>(loc, msgLen, diff);
    Value padSize2 = rewriter.create<AddIOp>(loc, padSize1, c64_I32);
    Value padSize = rewriter.create<SelectOp>(loc, isPadTwo, padSize2, padSize1);
    Value PadSizeIdx = rewriter.create<IndexCastOp>(loc, rewriter.getIndexType(), padSize);

    Value firstPaddingBlockIdx = rewriter.create<arith::DivUIOp>(loc, /*lhs=*/msgLenIdx, /*rhs=*/indexes[64]);
    Value blockNum = rewriter.create<arith::DivUIOp>(loc, /*lhs=*/PadSizeIdx, /*rhs=*/indexes[64]);
    Value lastPaddingBlockIdx = rewriter.create<arith::SubIOp>(loc, /*lhs=*/blockNum, /*rhs=*/indexes[1]);

    // Process each block
    scf::ForOp forOp = rewriter.create<scf::ForOp>(
      loc, c0, PadSizeIdx, c64, ValueRange{H0, H1, H2, H3, H4, H5, H6, H7},
      [&](OpBuilder &builder, Location loc, Value ivs, ValueRange iargs) {
        Value a = iargs[0], b = iargs[1], c = iargs[2], d = iargs[3],
              e = iargs[4], f = iargs[5], g = iargs[6], h = iargs[7];
        Value H0_last = iargs[0], H1_last = iargs[1], H2_last = iargs[2],
              H3_last = iargs[3], H4_last = iargs[4], H5_last = iargs[5],
              H6_last = iargs[6], H7_last = iargs[7];

        Value curBlockIdx = rewriter.create<arith::DivUIOp>(loc, /*lhs=*/ivs, /*rhs=*/indexes[64]);
        Value isPaddingNow = rewriter.create<arith::CmpIOp>(
            loc, /*predicate=*/arith::CmpIPredicate::eq, /*lhs=*/curBlockIdx,
            /*rhs=*/firstPaddingBlockIdx);
        Value isLastBlock = rewriter.create<arith::CmpIOp>(
            loc, /*predicate=*/arith::CmpIPredicate::eq, /*lhs=*/curBlockIdx,
            /*rhs=*/lastPaddingBlockIdx);
        scf::IfOp ifPaddingOp = rewriter.create<scf::IfOp>(
          loc, /*cond=*/isPaddingNow, /*thenBuilder=*/[&](OpBuilder &builder, Location loc) {
            Value loadingMask = rewriter.create<vector::CreateMaskOp>(
                loc, /*resultType0=*/vectorI1x64, /*operand=*/ValueRange{firstOneOffset});
            // load W[16] for each iteration
            Value zeroBlock = rewriter.create<vector::SplatOp>(
                loc, /*aggregate=*/vectorI8x64, /*input=*/ValueRange{c0I8});
            Value loadedData = rewriter.create<vector::MaskedLoadOp>(
                loc, /*result=*/vectorI8x64, /*base=*/plaintext,
                /*indices=*/ivs, /*mask=*/loadingMask, /*pass_thru=*/zeroBlock);
            Value paddedBlock = rewriter.create<vector::InsertElementOp>(
                loc, /*source=*/c_128I8, /*dest=*/loadedData, /*position=*/firstOneOffset);
            rewriter.create<scf::YieldOp>(
                loc, /*results=*/ValueRange{paddedBlock});
          },
          /*elseBuilder=*/
          [&](OpBuilder &builder, Location loc) {
            Value zeroBlock = rewriter.create<vector::SplatOp>(
                loc, /*aggregate=*/vectorI8x64, /*input=*/ValueRange{c0I8});
            Value loadedData = rewriter.create<vector::LoadOp>(
                loc, /*result=*/vectorI8x64, /*base=*/plaintext,
                /*indices=*/ivs);
            Value result = rewriter.create<arith::SelectOp>(
                loc, /*condition=*/isLastBlock, /*true_value=*/zeroBlock,
                /*false_value=*/loadedData);
            rewriter.create<scf::YieldOp>(loc, /*results=*/ValueRange{result});
          });

        scf::IfOp ifLastBlockOp = rewriter.create<scf::IfOp>(
          loc, /*cond=*/isLastBlock, /*thenBuilder=*/
          [&](OpBuilder &builder, Location loc) {
            Value extLen = rewriter.create<arith::ExtUIOp>(loc, /*out=*/rewriter.getI64Type(),/*in=*/msgLen);
            Value msgBits = rewriter.create<arith::MulIOp>(loc, /*lhs=*/extLen, /*rhs=*/c8_I64);
            Value t0 = rewriter.create<vector::BroadcastOp>(loc, /*vector=*/vectorI64x1, /*source=*/msgBits);
            Value t1 = rewriter.create<vector::BitCastOp>(loc, /*result=*/vectorI8x8, /*source=*/t0);
            SmallVector<int64_t> mask = {7, 6, 5, 4, 3, 2, 1, 0};
            Value t2 = rewriter.create<vector::ShuffleOp>(loc, /*v1=*/t1, /*v2=*/t1, /*odsArg2=*/mask);
            scf::ForOp innerForOp = rewriter.create<scf::ForOp>(
              loc, /*lowerBound=*/indexes[0], /*upperBound=*/indexes[8],
              /*step=*/indexes[1], /*iterArgs=*/ValueRange{ifPaddingOp.getResults()[0]},
              /*odsArg4=*/
              [&](OpBuilder &builder, Location loc, Value ivs, ValueRange iargs) {
                Value tailIdx = rewriter.create<arith::AddIOp>(loc, /*lhs=*/ivs, /*rhs=*/indexes[56]);
                Value t3 = rewriter.create<vector::ExtractElementOp>(loc, /*source=*/t2, /*position=*/ivs);
                Value t4 = rewriter.create<vector::InsertElementOp>(
                  loc, /*source=*/t3, /*dest=*/iargs[0], /*position=*/tailIdx);
                rewriter.create<scf::YieldOp>(loc, /*results=*/ValueRange{t4});
              });
              rewriter.create<scf::YieldOp>(loc, /*results=*/ValueRange{innerForOp.getResults()[0]});
            },
            /*elseBuilder=*/
            [&](OpBuilder &builder, Location loc) {
              rewriter.create<scf::YieldOp>(
                loc, /*results=*/ValueRange{ifPaddingOp.getResults()[0]});
            });
        Value paddedMsg = ifLastBlockOp.getResults()[0];

        SmallVector<int64_t> inv64{
          3,  2,  1,  0,  7,  6,  5,  4,  11, 10, 9,  8,  15, 14, 13, 12,
          19, 18, 17, 16, 23, 22, 21, 20, 27, 26, 25, 24, 31, 30, 29, 28,
          35, 34, 33, 32, 39, 38, 37, 36, 43, 42, 41, 40, 47, 46, 45, 44,
          51, 50, 49, 48, 55, 54, 53, 52, 59, 58, 57, 56, 63, 62, 61, 60};
        Value input0 = rewriter.create<vector::ShuffleOp>(loc, /*v1=*/paddedMsg, /*v2=*/paddedMsg, /*odsArg2=*/inv64);
        Value word = rewriter.create<vector::BitCastOp>(loc, vectorI32x16, input0);

        // expand W
        Value W[W_LEN];
        Value W1[W1_LEN];
        for (int i = 0; i < 16; i++) {
          W[i] = rewriter.create<vector::ExtractElementOp>(loc, word, indexes[i]);
        }
        for (int i = 16; i < W_LEN; i++) {
          Value xorTmp0 = rewriter.create<arith::XOrIOp>(loc, W[i - 16], W[i - 9]);
          // w[i-3] << 15
          Value leftShiftTmp1_ = rewriter.create<arith::ShLIOp>(loc, W[i - 3], c15_I32);
          Value leftShiftTmp1 = rewriter.create<arith::AndIOp>(loc, fullOneI32, leftShiftTmp1_);
          Value rightShiftTmp1 = rewriter.create<arith::ShRUIOp>(loc, W[i - 3], c17_I32);
          Value tmp1 = rewriter.create<arith::OrIOp>(loc, leftShiftTmp1, rightShiftTmp1);
          Value xorTmp1 = rewriter.create<arith::XOrIOp>(loc, xorTmp0, tmp1);

          // X<<<15
          Value leftShiftTmp2_ = rewriter.create<arith::ShLIOp>(loc, xorTmp1, c15_I32);
          Value leftShiftTmp2 = rewriter.create<arith::AndIOp>(loc, fullOneI32, leftShiftTmp2_);
          Value rightShiftTmp2 = rewriter.create<arith::ShRUIOp>(loc, xorTmp1, c17_I32);
          Value tmp2 = rewriter.create<arith::OrIOp>(loc, leftShiftTmp2, rightShiftTmp2);

          // X<<<23
          Value leftShiftTmp3_ = rewriter.create<arith::ShLIOp>(loc, xorTmp1, c23_I32);
          Value leftShiftTmp3 = rewriter.create<arith::AndIOp>(loc, fullOneI32, leftShiftTmp3_);

          Value rightShiftTmp3 = rewriter.create<arith::ShRUIOp>(loc, xorTmp1, c9_I32);
          Value tmp3 = rewriter.create<arith::OrIOp>(loc, leftShiftTmp3, rightShiftTmp3);

          Value tmp4 = rewriter.create<arith::XOrIOp>(loc, xorTmp1, tmp2);
          Value tmp5 = rewriter.create<arith::XOrIOp>(loc, tmp4, tmp3);
          Value leftShiftTmp4_ = rewriter.create<arith::ShLIOp>(loc, W[i - 13], c7_I32);
          Value leftShiftTmp4 = rewriter.create<arith::AndIOp>(loc, fullOneI32, leftShiftTmp4_);

          Value rightShiftTmp4 = rewriter.create<arith::ShRUIOp>(loc, W[i - 13], c25_I32);
          Value tmp6 = rewriter.create<arith::OrIOp>(loc, leftShiftTmp4, rightShiftTmp4);
          Value tmp7 = rewriter.create<arith::XOrIOp>(loc, tmp5, tmp6);

          W[i] = rewriter.create<arith::XOrIOp>(loc, W[i - 6], tmp7);
        }

        for (int i = 0; i < W1_LEN; i++) {
          W1[i] = rewriter.create<arith::XOrIOp>(loc, W[i], W[i + 4]);
        }

        Value T1 = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32IntegerAttr(0x79CC4519));
        Value T2 = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32IntegerAttr(0x7A879D8A));
        // start the main loop
        for (int i = 0; i < 16; i++) {
          // ss1 = ((a<<<12) + e + (tj<<<j))<<<7
          Value al12_ = rewriter.create<arith::ShLIOp>(loc, a, c12_I32);
          Value al12 = rewriter.create<arith::AndIOp>(loc, fullOneI32, al12_);
          Value ar20 = rewriter.create<arith::ShRUIOp>(loc, a, c20_I32);
          Value alr12 = rewriter.create<arith::OrIOp>(loc, al12, ar20);
          Value aAdde = rewriter.create<arith::AddIOp>(loc, alr12, e);
          Value shifti = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32IntegerAttr(i));
          Value righti = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32IntegerAttr((32 - i) % 32));

          Value shiftedValue_ = rewriter.create<arith::ShLIOp>(loc, T1, shifti);
          Value shiftedValue = rewriter.create<arith::AndIOp>(loc, fullOneI32, shiftedValue_);
          Value rightedValue = rewriter.create<arith::ShRUIOp>(loc, T1, righti);

          Value tmp1 = rewriter.create<arith::OrIOp>(loc, shiftedValue, rightedValue);
          Value tmp2 = rewriter.create<arith::AddIOp>(loc, tmp1, aAdde);

          Value shift1_ = rewriter.create<arith::ShLIOp>(loc, tmp2, c7_I32);
          Value shift1 = rewriter.create<arith::AndIOp>(loc, fullOneI32, shift1_);
          Value right1 = rewriter.create<arith::ShRUIOp>(loc, tmp2, c25_I32);
          Value ss1 = rewriter.create<arith::OrIOp>(loc, shift1, right1);
          // ss2 <- ss1 ^ (a<<<12)
          Value ss2 = rewriter.create<arith::XOrIOp>(loc, ss1, alr12);

          // tt1 = ((a ^ b ^ c) + d + ss2 + w1[j])<<<7
          Value aorb = rewriter.create<arith::XOrIOp>(loc, a, b);
          Value ffj = rewriter.create<arith::XOrIOp>(loc, aorb, c);
          Value tt1_tmp = rewriter.create<arith::AddIOp>(loc, ffj, d);
          Value tt1_tmp1 = rewriter.create<arith::AddIOp>(loc, tt1_tmp, ss2);
          Value tt1 = rewriter.create<arith::AddIOp>(loc, tt1_tmp1, W1[i]);

          // tt2 = ((e ^ f ^ g) + h + ss1 + w[j])<<<7
          Value eorf = rewriter.create<arith::XOrIOp>(loc, e, f);
          Value ggj = rewriter.create<arith::XOrIOp>(loc, eorf, g);
          Value tt2_tmp = rewriter.create<arith::AddIOp>(loc, ggj, h);
          Value tt2_tmp1 = rewriter.create<arith::AddIOp>(loc, tt2_tmp, ss1);
          Value tt2 = rewriter.create<arith::AddIOp>(loc, tt2_tmp1, W[i]);
          // d = c c=b<<<9
          d = c;
          Value shiftc_ = rewriter.create<arith::ShLIOp>(loc, b, c9_I32);
          Value shiftc = rewriter.create<arith::AndIOp>(loc, shiftc_, fullOneI32);
          Value rightc = rewriter.create<arith::ShRUIOp>(loc, b, c23_I32);
          Value tmpc = rewriter.create<arith::OrIOp>(loc, shiftc, rightc);
          c = tmpc;
          // b = a; a = tt1
          b = a;
          a = tt1;

          h = g;
          Value shiftg_ = rewriter.create<arith::ShLIOp>(loc, f, c19_I32);
          Value shiftg = rewriter.create<arith::AndIOp>(loc, shiftg_, fullOneI32);
          Value rightg = rewriter.create<arith::ShRUIOp>(loc, f, c13_I32);
          Value tmpg = rewriter.create<arith::OrIOp>(loc, shiftg, rightg);
          g = tmpg;

          f = e;

          Value left9_tt2_ = rewriter.create<arith::ShLIOp>(loc, tt2, c9_I32);
          Value right9_tt2 = rewriter.create<arith::ShRUIOp>(loc, tt2, c23_I32);
          Value left9_tt2 = rewriter.create<arith::AndIOp>(loc, left9_tt2_, fullOneI32);
          Value shift_tt2 = rewriter.create<arith::OrIOp>(loc, left9_tt2, right9_tt2);

          Value left17_tt2_ = rewriter.create<arith::ShLIOp>(loc, tt2, c17_I32);
          Value left17_tt2 = rewriter.create<arith::AndIOp>(loc, left17_tt2_, fullOneI32);
          Value right17_tt2 = rewriter.create<arith::ShRUIOp>(loc, tt2, c15_I32);
          Value shift_tt2_1 = rewriter.create<arith::OrIOp>(loc, left17_tt2, right17_tt2);

          Value tmp_tt2 = rewriter.create<arith::XOrIOp>(loc, shift_tt2, tt2);
          Value tmp_tt2_1 = rewriter.create<arith::XOrIOp>(loc, tmp_tt2, shift_tt2_1);
          e = tmp_tt2_1;
        }
        for (int i = 16; i < W1_LEN; i++) {
          // ss1 = ((a<<<12) + e + (tj<<<j))<<<7
          Value al12_ = rewriter.create<arith::ShLIOp>(loc, a, c12_I32);
          Value al12 = rewriter.create<arith::AndIOp>(loc, al12_, fullOneI32);
          Value ar20 = rewriter.create<arith::ShRUIOp>(loc, a, c20_I32);
          Value alr12 = rewriter.create<arith::OrIOp>(loc, al12, ar20);
          Value aAdde = rewriter.create<arith::AddIOp>(loc, alr12, e);
          Value shifti = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32IntegerAttr(i % 32));
          Value righti = rewriter.create<arith::ConstantOp>(loc, rewriter.getI32IntegerAttr(32 - (i % 32)));

          Value shiftedValue_ = rewriter.create<arith::ShLIOp>(loc, T2, shifti);
          Value shiftedValue = rewriter.create<arith::AndIOp>(loc, shiftedValue_, fullOneI32);
          Value rightedValue = rewriter.create<arith::ShRUIOp>(loc, T2, righti);
          Value tmp1 = rewriter.create<arith::OrIOp>(loc, shiftedValue, rightedValue);

          Value tmp2 = rewriter.create<arith::AddIOp>(loc, tmp1, aAdde);

          Value shift1_ = rewriter.create<arith::ShLIOp>(loc, tmp2, c7_I32);
          Value shift1 = rewriter.create<arith::AndIOp>(loc, shift1_, fullOneI32);
          Value right1 = rewriter.create<arith::ShRUIOp>(loc, tmp2, c25_I32);
          Value ss1 = rewriter.create<arith::OrIOp>(loc, shift1, right1);
          // ss2 <- ss1 ^ (a<<<12)
          Value ss2 = rewriter.create<arith::XOrIOp>(loc, ss1, alr12);

          // tt1 = ((a ^ b ^ c) + d + ss2 + w1[j])<<<7
          Value aandb = rewriter.create<arith::AndIOp>(loc, a, b);
          Value bandc = rewriter.create<arith::AndIOp>(loc, b, c);
          Value aandc = rewriter.create<arith::AndIOp>(loc, a, c);
          Value ffj_tmp = rewriter.create<arith::OrIOp>(loc, aandb, bandc);
          Value ffj = rewriter.create<arith::OrIOp>(loc, ffj_tmp, aandc);
          Value tt1_tmp = rewriter.create<arith::AddIOp>(loc, ffj, d);
          Value tt1_tmp1 = rewriter.create<arith::AddIOp>(loc, tt1_tmp, ss2);
          Value tt1 = rewriter.create<arith::AddIOp>(loc, tt1_tmp1, W1[i]);

          // tt2 = ((e ^ f ^ g) + h + ss1 + w[j])<<<7
          Value eandf = rewriter.create<arith::AndIOp>(loc, e, f);
          Value nege = rewriter.create<arith::XOrIOp>(loc, fullOneI32, e);
          Value neandg = rewriter.create<arith::AndIOp>(loc, nege, g);

          Value ggj = rewriter.create<arith::OrIOp>(loc, eandf, neandg);
          Value tt2_tmp = rewriter.create<arith::AddIOp>(loc, ggj, h);
          Value tt2_tmp1 = rewriter.create<arith::AddIOp>(loc, tt2_tmp, ss1);
          Value tt2 = rewriter.create<arith::AddIOp>(loc, tt2_tmp1, W[i]);
          // d = c c=b<<<9
          d = c;
          Value shiftc_ = rewriter.create<arith::ShLIOp>(loc, b, c9_I32);
          Value shiftc = rewriter.create<arith::AndIOp>(loc, shiftc_, fullOneI32);
          Value rightc = rewriter.create<arith::ShRUIOp>(loc, b, c23_I32);
          Value tmpc = rewriter.create<arith::OrIOp>(loc, shiftc, rightc);
          c = tmpc;
          // b = a; a = tt1
          b = a;
          a = tt1;

          h = g;
          Value shiftg_ = rewriter.create<arith::ShLIOp>(loc, f, c19_I32);
          Value shiftg = rewriter.create<arith::AndIOp>(loc, shiftg_, fullOneI32);
          Value rightg = rewriter.create<arith::ShRUIOp>(loc, f, c13_I32);
          Value tmpg = rewriter.create<arith::OrIOp>(loc, shiftg, rightg);
          g = tmpg;

          f = e;

          Value left9_tt2_ = rewriter.create<arith::ShLIOp>(loc, tt2, c9_I32);
          Value left9_tt2 = rewriter.create<arith::AndIOp>(loc, left9_tt2_, fullOneI32);
          Value right9_tt2 = rewriter.create<arith::ShRUIOp>(loc, tt2, c23_I32);
          Value shift_tt2 = rewriter.create<arith::OrIOp>(loc, left9_tt2, right9_tt2);

          Value left17_tt2_ = rewriter.create<arith::ShLIOp>(loc, tt2, c17_I32);
          Value left17_tt2 = rewriter.create<arith::AndIOp>(loc, left17_tt2_, fullOneI32);
          Value right17_tt2 = rewriter.create<arith::ShRUIOp>(loc, tt2, c15_I32);
          Value shift_tt2_1 = rewriter.create<arith::OrIOp>(loc, left17_tt2, right17_tt2);

          Value tmp_tt2 = rewriter.create<arith::XOrIOp>(loc, shift_tt2, tt2);
          Value tmp_tt2_1 = rewriter.create<arith::XOrIOp>(loc, tmp_tt2, shift_tt2_1);
          e = tmp_tt2_1;
        }

        a = rewriter.create<arith::XOrIOp>(loc, H0_last, a);
        b = rewriter.create<arith::XOrIOp>(loc, H1_last, b);
        c = rewriter.create<arith::XOrIOp>(loc, H2_last, c);
        d = rewriter.create<arith::XOrIOp>(loc, H3_last, d);
        e = rewriter.create<arith::XOrIOp>(loc, H4_last, e);
        f = rewriter.create<arith::XOrIOp>(loc, H5_last, f);
        g = rewriter.create<arith::XOrIOp>(loc, H6_last, g);
        h = rewriter.create<arith::XOrIOp>(loc, H7_last, h);

        builder.create<scf::YieldOp>(loc, ValueRange{a, b, c, d, e, f, g, h});
      });

    H0 = forOp.getResults()[0];
    H1 = forOp.getResults()[1];
    H2 = forOp.getResults()[2];
    H3 = forOp.getResults()[3];
    H4 = forOp.getResults()[4];
    H5 = forOp.getResults()[5];
    H6 = forOp.getResults()[6];
    H7 = forOp.getResults()[7];


    // store the final hash value to vector
    Value abcdefgh = rewriter.create<vector::SplatOp>(loc, H7, vectorI32x8);
    abcdefgh = rewriter.create<vector::InsertOp>(
        loc, /*source=*/H0, /*dest=*/abcdefgh, /*position=*/indexes[0]);
    abcdefgh = rewriter.create<vector::InsertOp>(
        loc, /*source=*/H1, /*dest=*/abcdefgh, /*position=*/indexes[1]);
    abcdefgh = rewriter.create<vector::InsertOp>(
        loc, /*source=*/H2, /*dest=*/abcdefgh, /*position=*/indexes[2]);
    abcdefgh = rewriter.create<vector::InsertOp>(
        loc, /*source=*/H3, /*dest=*/abcdefgh, /*position=*/indexes[3]);
    abcdefgh = rewriter.create<vector::InsertOp>(
        loc, /*source=*/H4, /*dest=*/abcdefgh, /*position=*/indexes[4]);
    abcdefgh = rewriter.create<vector::InsertOp>(
        loc, /*source=*/H5, /*dest=*/abcdefgh, /*position=*/indexes[5]);
    abcdefgh = rewriter.create<vector::InsertOp>(
        loc, /*source=*/H6, /*dest=*/abcdefgh, /*position=*/indexes[6]);
    abcdefgh = rewriter.create<vector::InsertOp>(
        loc, /*source=*/H7, /*dest=*/abcdefgh, /*position=*/indexes[7]);
    // store the final hash value from vector to memref
    SmallVector<int64_t> inv32{3,  2,  1,  0,  7,  6,  5,  4,  11, 10, 9,
                               8,  15, 14, 13, 12, 19, 18, 17, 16, 23, 22,
                               21, 20, 27, 26, 25, 24, 31, 30, 29, 28};
    abcdefgh = rewriter.create<vector::BitCastOp>(loc, vectorI8x32, abcdefgh);
    abcdefgh = rewriter.create<vector::ShuffleOp>(loc, abcdefgh, abcdefgh, inv32);
    rewriter.create<vector::StoreOp>(loc, abcdefgh, ciphertext, indexes[0]);

    rewriter.eraseOp(op);
    return success();
  }
};

} // end SM3 namespace.

// AES
namespace {
  
Value GFMultiplyByte(Location loc, PatternRewriter &rewriter, Value m, Value c) {
  Value cx00 = rewriter.create<ConstantOp>(loc, rewriter.getI8Type(), rewriter.getI8IntegerAttr(0x00));
  Value cx01 = rewriter.create<ConstantOp>(loc, rewriter.getI8Type(), rewriter.getI8IntegerAttr(0x01));
  Value cx1b = rewriter.create<ConstantOp>(loc, rewriter.getI8Type(), rewriter.getI8IntegerAttr(0x1b));
  Value cx80 = rewriter.create<ConstantOp>(loc, rewriter.getI8Type(), rewriter.getI8IntegerAttr(0x80));

  Value result = cx00;
  Value temp = m;
  for (int i = 0; i < 8; ++i) {
    Value resAnd = rewriter.create<arith::AndIOp>(loc, c, cx01);
    Value isXor = rewriter.create<arith::CmpIOp>(loc, arith::CmpIPredicate::ne, resAnd, cx00);
    Value resultXor = rewriter.create<arith::XOrIOp>(loc, result, temp);
    result = rewriter.create<arith::SelectOp>(loc, isXor, resultXor, result);

    Value tempHighBit = rewriter.create<arith::AndIOp>(loc, temp, cx80);
    Value tempShifted = rewriter.create<arith::ShLIOp>(loc, temp, cx01);
    Value tempReduced = rewriter.create<arith::XOrIOp>(loc, tempShifted, cx1b);
    Value isReduce = rewriter.create<arith::CmpIOp>(loc, arith::CmpIPredicate::ne, tempHighBit, cx00);
    temp = rewriter.create<arith::SelectOp>(loc, isReduce, tempReduced, tempShifted);

    c = rewriter.create<arith::ShRUIOp>(loc, c, cx01);
  }

  return result;
}

class AESLoweringX86Pattern : public OpRewritePattern<crypto::AESOP> {
 public:
  using OpRewritePattern<crypto::AESOP>::OpRewritePattern;

  LogicalResult matchAndRewrite(crypto::AESOP op,
                                PatternRewriter &rewriter) const override {
    CHECK_ATTR(op)
    auto loc = op.getLoc();
    Value message = op->getOperand(0);    // memref<?xi8>
    Value messageLen = op->getOperand(1); // i32
    Value output = op->getOperand(2);     // memref<?xi8>
    Value expandKey = op->getOperand(3);  // memref<176xi8>
    Value sBoxBase = op->getOperand(4);   // memref<272xi8>
    
    VectorType vectorI1x16 = VectorType::get({16}, rewriter.getI1Type());
    VectorType vectorI8x4 = VectorType::get({4}, rewriter.getI8Type());
    VectorType vectorI8x16 = VectorType::get({16}, rewriter.getI8Type());
    VectorType vectorI32x16 = VectorType::get({16}, rewriter.getI32Type());
    Value c0I8 = rewriter.create<ConstantOp>(loc, rewriter.getI8Type(), rewriter.getI8IntegerAttr(0));
    Value c16I8 = rewriter.create<ConstantOp>(loc, rewriter.getI8Type(), rewriter.getI8IntegerAttr(16));
    Value c16I32 = rewriter.create<ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(16));
    Value M[4], C[4], X[4];
    Value cindex[17];
    for (int i = 0; i <= 16; ++ i) cindex[i] = rewriter.create<ConstantIndexOp>(loc, i);
    Value maskI1x16 = rewriter.create<vector::CreateMaskOp>(loc, vectorI1x16, ValueRange{cindex[16]});
    
    // load mixColumnMatrix into M[4]
    for (int i = 256, j = 0; j < 16; j += 4) {
      Value index = rewriter.create<ConstantIndexOp>(loc, i + j);
      M[j / 4] = rewriter.create<vector::LoadOp>(loc, vectorI8x4, sBoxBase, index);
    }

    // // crypto or crack branch
    // Value firstValue = rewriter.create<memref::LoadOp>(loc, sBoxBase, cindex[0]);
    // Value iscCrypto = rewriter.create<arith::CmpIOp>(loc, arith::CmpIPredicate::eq, firstValue, rewriter.create<ConstantOp>(loc, rewriter.getI8Type(), rewriter.getI8IntegerAttr(0x63)));
    // scf::IfOp ifPaddingOp = rewriter.create<scf::IfOp>(loc, /*cond=*/iscCrypto, 
    //   /*thenBuilder=*/[&](OpBuilder &builder, Location loc) {

    //     // --------------------------------------crypto of aes begin--------------------------------------
    //     SmallVector<int64_t, 16> inv16{0,    5,   10,   15,
    //                                    4,    9,   14,    3,
    //                                    8,   13,    2,    7,
    //                                   12,    1,    6,   11}; 
        
    //     // PKCS #7 (Padding Cryptography System 7)
    //     Value rem = rewriter.create<arith::RemSIOp>(loc, /*lhs=*/messageLen, /*rhs=*/c16I32);
    //     Value firstOneOffset = rewriter.create<arith::IndexCastOp>(loc, rewriter.getIndexType(), rem);
    //     Value diff = rewriter.create<arith::SubIOp>(loc, /*lhs=*/c16I32, /*rhs=*/rem);
    //     Value padSize = rewriter.create<arith::AddIOp>(loc, /*lhs=*/messageLen, /*rhs=*/diff);
    //     Value padSizeIdx = rewriter.create<arith::IndexCastOp>(loc, rewriter.getIndexType(), padSize);
    //     Value beginPaddingSizeIdx = rewriter.create<arith::SubIOp>(loc, /*lhs=*/padSizeIdx, /*rhs=*/cindex[16]);

    //     // Iterate over each block
    //     scf::ForOp forOp = rewriter.create<scf::ForOp>(
    //       loc, cindex[0], padSizeIdx, cindex[16], ValueRange{}, [&](OpBuilder &builder, Location loc, Value ivs, ValueRange iargs){
            
    //         // decide whether or not to do the padding
    //         Value isPadding = rewriter.create<arith::CmpIOp>(loc, /*predicate=*/arith::CmpIPredicate::eq, /*lhs=*/ivs, /*rhs=*/beginPaddingSizeIdx);
    //         scf::IfOp ifPaddingOp = rewriter.create<scf::IfOp>(
    //           loc, /*cond=*/isPadding, /*thenBuilder=*/[&](OpBuilder &builder, Location loc) {
    //             Value mask = rewriter.create<vector::CreateMaskOp>(loc, /*resultType0=*/vectorI1x16, /*operand=*/ValueRange{firstOneOffset});
    //             Value diff_I8 = rewriter.create<arith::TruncIOp>(loc, rewriter.getI8Type(), diff);
    //             Value diffBlock = rewriter.create<vector::SplatOp>(loc, /*aggregate=*/vectorI8x16, /*input=*/diff_I8);
    //             Value loadedData = rewriter.create<vector::MaskedLoadOp>(loc, /*result=*/vectorI8x16, /*base=*/message, /*indices=*/ivs, /*mask=*/mask, /*pass_thru=*/diffBlock);
    //             rewriter.create<scf::YieldOp>(loc, /*results=*/ValueRange{loadedData});
    //           },
    //           /*elseBuilder=*/[&](OpBuilder &builder, Location loc) {
    //             Value loadedData = rewriter.create<vector::LoadOp>(loc, /*result=*/vectorI8x16, /*base=*/message, /*indices=*/ivs);
    //             rewriter.create<scf::YieldOp>(loc, /*results=*/ValueRange{loadedData});
    //           }
    //         );

    //         /***************************************core algorithm***************************************/
    //         Value plainText = ifPaddingOp.getResults()[0];
    //         // Value cipherText = addRoundKey(loc, rewriter, plainText, expandKey, c0);
    //         Value keySlice = rewriter.create<vector::LoadOp>(loc, vectorI8x16, expandKey, cindex[0]);
    //         Value cipherText = rewriter.create<arith::XOrIOp>(loc, plainText, keySlice);

    //         for (int i = 16; i < 160; i += 16) {
    //           Value ci = rewriter.create<ConstantIndexOp>(loc, i);
    //           //   cipherText = shiftRows(loc, rewriter, cipherText, sBoxBase, inv16);
    //           Value cipherTextIndex = rewriter.create<arith::ExtUIOp>(loc, vectorI32x16, cipherText);
    //           cipherText = rewriter.create<vector::GatherOp>(loc, vectorI8x16, sBoxBase, cindex[0], cipherTextIndex, maskI1x16, cipherText);
    //           cipherText = rewriter.create<vector::ShuffleOp>(loc, cipherText, cipherText, inv16);

    //           //   cipherText = mixColumns(loc, rewriter, cipherText);
    //           for (int j = 0; j < 16; j += 4) {
    //             C[j / 4] = rewriter.create<vector::ExtractStridedSliceOp>(loc, cipherText, int64_t(j), int64_t(4), int64_t(1));
    //           }
    //           for (int j = 0; j < 4; ++ j) {
    //             for (int k = 0; k < 4; ++ k) {
    //               for (int l = 0; l < 4; ++ l) {
    //                 Value m = rewriter.create<vector::ExtractElementOp>(loc, M[k], cindex[l]);
    //                 Value c = rewriter.create<vector::ExtractElementOp>(loc, C[j], cindex[l]);
    //                 X[l] = GFMultiplyByte(loc, rewriter, m, c);
    //               }
    //               cipherText = rewriter.create<vector::InsertElementOp>(loc, vectorI8x16, rewriter.create<arith::XOrIOp>(loc, 
    //                   rewriter.create<arith::XOrIOp>(loc, X[0], X[1]), 
    //                   rewriter.create<arith::XOrIOp>(loc, X[2], X[3])), cipherText, cindex[4 * j + k]);
    //             }
    //           }

    //           //   cipherText = addRoundKey(loc, rewriter, cipherText, expandKey, ci);
    //           keySlice = rewriter.create<vector::LoadOp>(loc, vectorI8x16, expandKey, ci);
    //           cipherText = rewriter.create<arith::XOrIOp>(loc, cipherText, keySlice);
    //         }

    //         // cipherText = shiftRows(loc, rewriter, cipherText, sBoxBase, inv16);
    //         Value cipherTextIndex = rewriter.create<arith::ExtUIOp>(loc, vectorI32x16, cipherText);
    //         cipherText = rewriter.create<vector::GatherOp>(loc, vectorI8x16, sBoxBase, cindex[0], cipherTextIndex, maskI1x16, cipherText);
    //         cipherText = rewriter.create<vector::ShuffleOp>(loc, cipherText, cipherText, inv16);

    //         // cipherText = addRoundKey(loc, rewriter, cipherText, expandKey, c160);
    //         Value c160 = rewriter.create<ConstantIndexOp>(loc, 160);
    //         keySlice = rewriter.create<vector::LoadOp>(loc, vectorI8x16, expandKey, c160);
    //         cipherText = rewriter.create<arith::XOrIOp>(loc, cipherText, keySlice);
    //         /***************************************core algorithm***************************************/
    //         // store
    //         rewriter.create<vector::StoreOp>(loc, cipherText, output, ivs);
    //         rewriter.create<scf::YieldOp>(loc);
    //       }
    //     );
    //     // --------------------------------------crypto of aes over--------------------------------------
    //     rewriter.create<scf::YieldOp>(loc);
    //   }, /*elseBuilder=*/[&](OpBuilder &builder, Location loc) {
        // --------------------------------------crack of aes begin--------------------------------------
        SmallVector<int64_t, 16> inv16{0,   13,   10,    7,
                                       4,    1,   14,   11,
                                       8,    5,    2,   15,
                                      12,    9,    6,    3}; 
        
        Value padSizeIdx = rewriter.create<arith::IndexCastOp>(loc, rewriter.getIndexType(), messageLen);
        Value beginPaddingSizeIdx = rewriter.create<arith::SubIOp>(loc, /*lhs=*/padSizeIdx, /*rhs=*/cindex[16]);

        scf::ForOp forOp = rewriter.create<scf::ForOp>(
          loc, cindex[0], padSizeIdx, cindex[16], ValueRange{}, [&](OpBuilder &builder, Location loc, Value ivs, ValueRange iargs){
            
            /***************************************core algorithm***************************************/
            Value plainText = rewriter.create<vector::LoadOp>(loc, vectorI8x16, message, ivs);
            // cipherText = addRoundKey(loc, rewriter, cipherText, expandKey, c160);
            Value c160 = rewriter.create<ConstantIndexOp>(loc, 160);
            Value keySlice = rewriter.create<vector::LoadOp>(loc, vectorI8x16, expandKey, c160);
            Value cipherText = rewriter.create<arith::XOrIOp>(loc, plainText, keySlice);

            for (int i = 144; i > 0; i -= 16) {
              Value ci = rewriter.create<ConstantIndexOp>(loc, i);
              //   cipherText = invShiftRows(loc, rewriter, cipherText, invsBoxBase, inv16);
              Value cipherTextIndex = rewriter.create<arith::ExtUIOp>(loc, vectorI32x16, cipherText);
              cipherText = rewriter.create<vector::GatherOp>(loc, vectorI8x16, sBoxBase, cindex[0], cipherTextIndex, maskI1x16, cipherText);
              cipherText = rewriter.create<vector::ShuffleOp>(loc, cipherText, cipherText, inv16);

              //   cipherText = addRoundKey(loc, rewriter, cipherText, expandKey, ci);
              keySlice = rewriter.create<vector::LoadOp>(loc, vectorI8x16, expandKey, ci);
              cipherText = rewriter.create<arith::XOrIOp>(loc, cipherText, keySlice);

              //   cipherText = invMixColumns(loc, rewriter, cipherText);
              for (int j = 0; j < 16; j += 4) {
                C[j / 4] = rewriter.create<vector::ExtractStridedSliceOp>(loc, cipherText, int64_t(j), int64_t(4), int64_t(1));
              }
              for (int j = 0; j < 4; ++ j) {
                for (int k = 0; k < 4; ++ k) {
                  for (int l = 0; l < 4; ++ l) {
                    Value m = rewriter.create<vector::ExtractElementOp>(loc, M[k], cindex[l]);
                    Value c = rewriter.create<vector::ExtractElementOp>(loc, C[j], cindex[l]);
                    X[l] = GFMultiplyByte(loc, rewriter, m, c);
                  }
                  cipherText = rewriter.create<vector::InsertElementOp>(loc, vectorI8x16, rewriter.create<arith::XOrIOp>(loc, 
                      rewriter.create<arith::XOrIOp>(loc, X[0], X[1]), 
                      rewriter.create<arith::XOrIOp>(loc, X[2], X[3])), cipherText, cindex[4 * j + k]);
                }
              }
            }
            // cipherText = invShiftRows(loc, rewriter, cipherText, sBoxBase, inv16);
            Value cipherTextIndex = rewriter.create<arith::ExtUIOp>(loc, vectorI32x16, cipherText);
            cipherText = rewriter.create<vector::GatherOp>(loc, vectorI8x16, sBoxBase, cindex[0], cipherTextIndex, maskI1x16, cipherText);
            cipherText = rewriter.create<vector::ShuffleOp>(loc, cipherText, cipherText, inv16);

            // cipherText = addRoundKey(loc, rewriter, cipherText, expandKey, c0);
            keySlice = rewriter.create<vector::LoadOp>(loc, vectorI8x16, expandKey, cindex[0]);
            cipherText = rewriter.create<arith::XOrIOp>(loc, cipherText, keySlice);
            /***************************************core algorithm***************************************/
            
            // decide whether or not to do the padding
            Value isPadding = rewriter.create<arith::CmpIOp>(loc, /*predicate=*/arith::CmpIPredicate::eq, /*lhs=*/ivs, /*rhs=*/beginPaddingSizeIdx);
            scf::IfOp ifPaddingOp = rewriter.create<scf::IfOp>(
              loc, /*cond=*/isPadding, /*thenBuilder=*/[&](OpBuilder &builder, Location loc) {
                Value padSize = rewriter.create<vector::ExtractElementOp>(loc, cipherText, cindex[15]);
                Value diff = rewriter.create<arith::SubIOp>(loc, c16I8, padSize);
                Value diffIndex = rewriter.create<arith::IndexCastOp>(loc, rewriter.getIndexType(), diff);
                Value mask = rewriter.create<vector::CreateMaskOp>(loc, vectorI1x16, ValueRange{diffIndex});
                rewriter.create<vector::MaskedStoreOp>(loc, output, ivs, mask, cipherText);
                rewriter.create<scf::YieldOp>(loc);
              },
              /*elseBuilder=*/[&](OpBuilder &builder, Location loc) {
                rewriter.create<vector::StoreOp>(loc, cipherText, output, ivs);
                rewriter.create<scf::YieldOp>(loc);
              }
            );

            // store
            rewriter.create<scf::YieldOp>(loc);
          }
        );
        // --------------------------------------crack of aes over--------------------------------------
    //     rewriter.create<scf::YieldOp>(loc);
    //   }
    // );

    rewriter.eraseOp(op);
    return success();                  
  }
};
}

void populateConvertCryptoToX86WithouSHA1ISAPatterns(RewritePatternSet &patterns) {
  patterns.add<Sha1coreLoweringWithoutSHA1ISAPattern>(patterns.getContext());
  patterns.add<Sha1LoweringWithoutSHA1ISAPattern>(patterns.getContext());
  patterns.add<CmpOpConversion>(patterns.getContext());
  patterns.add<MD5LoweringX86Pattern>(patterns.getContext());
  patterns.add<SM3LoweringX86Pattern>(patterns.getContext());
  patterns.add<AESLoweringX86Pattern>(patterns.getContext());
}

namespace {
class LowerCryptoToX86WithoutSHA1ISAPass : public PassWrapper<LowerCryptoToX86WithoutSHA1ISAPass, OperationPass<ModuleOp>> {
public:
  MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(LowerCryptoToX86WithoutSHA1ISAPass)
  LowerCryptoToX86WithoutSHA1ISAPass() = default;
  LowerCryptoToX86WithoutSHA1ISAPass(const LowerCryptoToX86WithoutSHA1ISAPass &) {}

  StringRef getArgument() const final { return "lower-cryptosha1-without-SHA1ISA"; }
  StringRef getDescription() const final { return "Lower Crypto SHA1 to common."; }

  void runOnOperation() override;

  void getDependentDialects(DialectRegistry &registry) const override {
    registry
        .insert<func::FuncDialect,
                mlir::LLVM::LLVMDialect,
                memref::MemRefDialect,
                vector::VectorDialect,
                arith::ArithDialect,
                affine::AffineDialect,
                scf::SCFDialect
                >();
  }
};
} // end anonymous namespace.

void LowerCryptoToX86WithoutSHA1ISAPass::runOnOperation() {
  MLIRContext *context = &getContext();
  ModuleOp module = getOperation();

  ConversionTarget target(*context);
  target.addLegalDialect<
  func::FuncDialect,
  vector::VectorDialect,
  mlir::LLVM::LLVMDialect,
  memref::MemRefDialect,
  arith::ArithDialect,
  affine::AffineDialect,
  scf::SCFDialect
  >();
  target.addLegalOp<
  ModuleOp,
  func::FuncOp,
  func::ReturnOp
  >();

  RewritePatternSet patterns(context);
  populateConvertCryptoToX86WithouSHA1ISAPatterns(patterns);

  if (failed(applyPartialConversion(module, target, std::move(patterns))))
    signalPassFailure();
}

void registerLowerCryptoToX86WithoutSHA1ISAPass() { PassRegistration<LowerCryptoToX86WithoutSHA1ISAPass>(); }

std::unique_ptr<Pass> createLowerCryptoToX86WithoutSHA1ISAPass(){
  return std::make_unique<LowerCryptoToX86WithoutSHA1ISAPass>();
}
